feat: first somewhat working version
This commit is contained in:
1 parent
57e063eacb
commit
e12694bf0a
13 files changed
+3968
-1
No files matched your search
+15
@@ -0,0 +1,15 @@
|
||||
import express from 'express';
|
||||
|
||||
export const ensureAuth = ( redirectOnError?: string ) => {
|
||||
return ( request: express.Request, response: express.Response, next: express.NextFunction ) => {
|
||||
if ( request.isAuthenticated() ) {
|
||||
next();
|
||||
} else {
|
||||
if ( redirectOnError ) {
|
||||
response.redirect( redirectOnError );
|
||||
} else {
|
||||
response.sendStatus( 401 );
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface User {
|
||||
'id': string;
|
||||
'username'?: string;
|
||||
'displayName'?: string;
|
||||
'emails'?: {
|
||||
'value': string,
|
||||
'type'?: string
|
||||
}[];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface OIDCConfig {
|
||||
'clientID': string;
|
||||
'clientSecret': string;
|
||||
'issuer': URL;
|
||||
'scopes'?: ( 'email' | 'profile' )[],
|
||||
}
|
||||
|
||||
export interface AppConfig {
|
||||
'failRedirect'?: string;
|
||||
'successRedirect'?: string;
|
||||
'logoutRedirect'?: string;
|
||||
'url': URL;
|
||||
'sessionSecret': string;
|
||||
'cookieName'?: string;
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
'oidc': OIDCConfig;
|
||||
'prod': boolean;
|
||||
'app': AppConfig
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import express from 'express';
|
||||
|
||||
export * from './routes.js';
|
||||
|
||||
export * from './auth.js';
|
||||
|
||||
export type * from './dtype.d.ts';
|
||||
|
||||
|
||||
export const getUserId = ( request: express.Request ): string | undefined => {
|
||||
return request.user?.id;
|
||||
};
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
import {
|
||||
Config
|
||||
} from './dtype.js';
|
||||
import OIDCStrategy from 'passport-openidconnect';
|
||||
import {
|
||||
discovery
|
||||
} from 'openid-client';
|
||||
import {
|
||||
ensureAuth
|
||||
} from './auth.js';
|
||||
import express from 'express';
|
||||
import passport from 'passport';
|
||||
import session from 'express-session';
|
||||
|
||||
/**
|
||||
* Add the necessary routes for login
|
||||
* @param app - Express application to register routes to
|
||||
* @param config - Configuration for the Auth system
|
||||
* @param verify - A verification function. Should create user if user doesn't exist
|
||||
* @param getUser - Function to get a user by user ID. Optional, if not provided, only UID will be recovered (should be good enough in most cases)
|
||||
*/
|
||||
export const addRoutes = async (
|
||||
app: express.Application,
|
||||
config: Config,
|
||||
verify: ( profile: OIDCStrategy.Profile ) => Promise<boolean>,
|
||||
getUser?: ( id: string ) => Promise<Express.User>
|
||||
) => {
|
||||
app.use( session( {
|
||||
'secret': config.app.sessionSecret,
|
||||
'cookie': {
|
||||
'httpOnly': true,
|
||||
'secure': config.prod
|
||||
},
|
||||
'resave': false,
|
||||
'saveUninitialized': false
|
||||
} ) );
|
||||
app.use( passport.session() );
|
||||
app.use( passport.initialize() );
|
||||
|
||||
passport.serializeUser( ( user, cb ) => {
|
||||
cb( null, user.id );
|
||||
} );
|
||||
|
||||
if ( getUser )
|
||||
passport.deserializeUser<string>( async ( user, cb ) => {
|
||||
return cb( null, await getUser( user ) );
|
||||
} );
|
||||
else
|
||||
passport.deserializeUser<string>( ( user, cb ) => {
|
||||
return cb( null, {
|
||||
'id': user
|
||||
} );
|
||||
} );
|
||||
|
||||
const oidcServerConfig = ( await discovery( config.oidc.issuer, config.oidc.clientID, config.oidc.clientSecret ) ).serverMetadata();
|
||||
const iss = config.oidc.issuer.toString();
|
||||
|
||||
passport.use( new OIDCStrategy(
|
||||
{
|
||||
'clientID': config.oidc.clientID,
|
||||
'clientSecret': config.oidc.clientSecret,
|
||||
'callbackURL': config.app.url.toString() + 'auth/v2/verify',
|
||||
'authorizationURL': oidcServerConfig.authorization_endpoint ?? oidcServerConfig.issuer + '/auth',
|
||||
'issuer': iss,
|
||||
'userInfoURL': oidcServerConfig.userinfo_endpoint ?? oidcServerConfig.issuer + '/me',
|
||||
'tokenURL': oidcServerConfig.token_endpoint ?? oidcServerConfig.issuer + '/token',
|
||||
'scope': config.oidc.scopes
|
||||
},
|
||||
async ( issuer: string, profile: OIDCStrategy.Profile, cb: OIDCStrategy.VerifyCallback ) => {
|
||||
if ( issuer !== iss ) cb( new Error( 'ERR_FORBIDDEN' ) );
|
||||
|
||||
cb( null, profile, await verify( profile ) );
|
||||
}
|
||||
) );
|
||||
|
||||
// TODO: Allow CORS here
|
||||
app.get( '/auth/v2/login', passport.authenticate( 'openidconnect' ) );
|
||||
|
||||
app.get( '/auth/v2/fail', ( _request, response ) => {
|
||||
response.sendFile( './public/error-page.html' );
|
||||
} );
|
||||
|
||||
app.get( '/auth/v2/verify', passport.authenticate( 'openidconnect', {
|
||||
'failureRedirect': config.app.failRedirect ?? '/auth/v2/fail',
|
||||
'failureMessage': true
|
||||
} ), ( request: express.Request, response: express.Response ) => {
|
||||
request.session.save();
|
||||
response.redirect( config.app.successRedirect ?? '/' );
|
||||
} );
|
||||
|
||||
// TODO: Allow CORS here
|
||||
app.get( '/auth/v2/logout', ensureAuth( config.app.failRedirect ), ( request: express.Request, response: express.Response ) => {
|
||||
request.logout( {
|
||||
'keepSessionInfo': false
|
||||
}, err => {
|
||||
console.error( err );
|
||||
} );
|
||||
|
||||
// Ensure session is fully destroyed
|
||||
request.session.destroy( () => {} );
|
||||
response.redirect( config.app.logoutRedirect ?? '/' );
|
||||
} );
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
addRoutes,
|
||||
ensureAuth
|
||||
} from './index.js';
|
||||
import express from 'express';
|
||||
|
||||
const app = express();
|
||||
|
||||
addRoutes( app, {
|
||||
'oidc': {
|
||||
'clientID': process.env.CLIENT_ID ?? '',
|
||||
'clientSecret': process.env.CLIENT_SECRET ?? '',
|
||||
'issuer': new URL( 'https://home.janishutz.com/oidc' )
|
||||
},
|
||||
'app': {
|
||||
'sessionSecret': 'secret',
|
||||
'url': new URL( 'https://home2.janishutz.com' ),
|
||||
'successRedirect': '/account'
|
||||
},
|
||||
'prod': false
|
||||
}, async uid => {
|
||||
console.log( uid );
|
||||
|
||||
return true;
|
||||
} );
|
||||
|
||||
app.get( '/', ( _request, response ) => {
|
||||
response.send( 'Hello World' );
|
||||
} );
|
||||
|
||||
app.get( '/account', ensureAuth( '/' ), ( _request, response ) => {
|
||||
response.send( 'Account' );
|
||||
} );
|
||||
|
||||
app.listen( 8080 );
|
||||
Reference in new issue
Block a user