feat: demo application

This commit is contained in:
janishutz committed 2026-09-10 13:33:58 +02:00
1 parent e12694bf0a
commit 355aab7f6f
13 files changed
+1494 -569

No files matched your search

-15
View File
@@ -1,15 +0,0 @@
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 );
}
}
};
};
+64
View File
@@ -0,0 +1,64 @@
import {
CorsOptions
} from 'cors';
import cors from 'cors';
let corsWhitelist: string[] = [];
/**
* Set the URLs that are allowed to send CORS requests
* @param whitelist - An array of URLs that are allowed to send CORS requests
*/
const setWhitelist = ( whitelist: string[] ) => {
corsWhitelist = whitelist;
};
/**
* Express.js middleware for cors verification
* @param rest - Whether or not to allow REST requests or not (default = false)
*/
const middleware = ( rest: boolean = false ) => {
return cors( generateOpts( rest ) );
};
/**
* Generate the options for the CORS library
* @param rest - Whether to allow REST requests or not (default = false)
* @returns CORS options object that can be used for the cors library
*/
const generateOpts = ( rest: boolean = false ): CorsOptions => {
const corsOpts: CorsOptions = {
'credentials': true,
'optionsSuccessStatus': 200,
'origin': ( origin, cb ) => {
const status = blockREST( origin! );
cb( null, status );
}
};
if ( rest ) {
corsOpts[ 'origin' ] = ( origin, cb ) => {
const status = allowREST( origin! );
cb( null, status );
};
}
return corsOpts;
};
const blockREST = ( origin: string ): boolean => {
return corsWhitelist.includes( origin );
};
const allowREST = ( origin: string ): boolean => {
return blockREST( origin ) || !origin;
};
export default {
generateOpts,
setWhitelist,
middleware
};
-36
View File
@@ -1,36 +0,0 @@
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
}
+21 -6
View File
@@ -1,12 +1,27 @@
import connect from 'express-openid-connect';
import express from 'express';
export * from './routes.js';
const app = express();
export * from './auth.js';
app.use( connect.auth( {
'authRequired': false,
'issuerBaseURL': process.env.OIDC_ISSUER,
'clientID': process.env.OIDC_CLIENT_ID,
'clientSecret': process.env.OIDC_CLIENT_SECRET,
'secret': process.env.SIGNING_SECRET,
'baseURL': process.env.APP_BASE_URL,
'authorizationParams': {
'scope': 'openid profile email',
'response_type': 'code'
}
} ) );
export type * from './dtype.d.ts';
app.get( '/', ( _request, response ) => {
response.send( 'Hello World' );
} );
app.get( '/account', connect.requiresAuth(), ( request, response ) => {
response.send( 'Account' );
} );
export const getUserId = ( request: express.Request ): string | undefined => {
return request.user?.id;
};
app.listen( 8080 );
-103
View File
@@ -1,103 +0,0 @@
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 ?? '/' );
} );
};
-35
View File
@@ -1,35 +0,0 @@
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 );