Skip to content

Commit

Permalink
login callback separate logic, session log in exception filter, manua…
Browse files Browse the repository at this point in the history
…l session key setup for oidc, extended logging, session cookie domain set for production environments
  • Loading branch information
SebastianW committed Oct 22, 2024
1 parent dd8f6b5 commit 7f0ed8e
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 43 deletions.
2 changes: 1 addition & 1 deletion libs/oidc/src/controllers/login-callback.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ export class LoginCallbackController {
@Public()
@Get()
loginCallback(@Req() req: Request, @Res() res: Response, @Next() next: Function, @Param() params) {
this.oidcService.login(req, res, next, params);
this.oidcService.loginCallback(req, res, next, params);
}
}
4 changes: 2 additions & 2 deletions libs/oidc/src/filters/http-exception.filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ export class HttpExceptionFilter implements ExceptionFilter {
const request = ctx.getRequest();
const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR;

const { body, headers, method, params, query, url, user } = request;
const { body, headers, method, params, query, url, user, session } = request;

this.logger.error({ request: { body, headers, method, params, query, url, user }, exception });
this.logger.error({ request: { body, headers, method, params, query, url, user, session }, exception });

switch (status) {
case MisdirectedStatus.MISDIRECTED:
Expand Down
82 changes: 43 additions & 39 deletions libs/oidc/src/services/oidc.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@ import passport = require('passport');
// const url = require('url');

const _loginCallbackPath = 'login/callback';
// const _westEU = 'westeurope';
// const _northEU = 'northeurope';

@Injectable()
export class OidcService implements OnModuleInit {
readonly logger = new Logger(OidcService.name);
#instanceID: string;
#oidc_redirect_uri: string;
#region: string;
#redirect_uri: string;
#sessionKey: string;
isMultitenant: boolean = false;
strategy: any;
#instanceID: string;
idpInfos: {
[tokenName: string]: {
trustIssuer: Issuer<Client>;
Expand All @@ -42,7 +45,8 @@ export class OidcService implements OnModuleInit {
this.isMultitenant = !!this.options.issuerOrigin;
this.#instanceID = configService.get('SERVER_INSTANCE_ID');
this.#region = configService.get('REGION_NAME');
this.#redirect_uri = configService.get('OIDC_REDIRECT_URI') ? `${configService.get('OIDC_REDIRECT_URI')}/${_loginCallbackPath}` : `${this.options.origin}/${_loginCallbackPath}`;
this.#sessionKey = `oidc:${configService.get('APIM_BASE_URL').replace('https://', '')}`;
this.#oidc_redirect_uri = configService.get('OIDC_REDIRECT_URI') ? `${configService.get('OIDC_REDIRECT_URI')}` : `${this.options.origin}`;
}

async onModuleInit() {
Expand Down Expand Up @@ -85,10 +89,13 @@ export class OidcService implements OnModuleInit {
tokenStore,
strategy,
};
this.options.authParams.redirect_uri = this.#redirect_uri;
this.options.authParams.redirect_uri = `${this.#oidc_redirect_uri}/${_loginCallbackPath}`;
this.options.authParams.nonce = this.options.authParams.nonce === 'true' ? uuid() : this.options.authParams.nonce;

strategy = new OidcStrategy(this, key, channelType);
// const hostname = url.parse(trustIssuer.issuer).hostname;
// this.#sessionKey = this.#buildSessionKey({ key: this.#sessionKey, hostname });

strategy = new OidcStrategy(this, key, this.#sessionKey, channelType);
this.idpInfos[key].strategy = strategy;

return strategy;
Expand Down Expand Up @@ -126,16 +133,16 @@ export class OidcService implements OnModuleInit {
}

async login(@Req() req: Request, @Res() res: Response, @Next() next: Function, @Param() params) {
this.#sessionLog({ name: 'LOGIN', req, instanceID: this.#instanceID, region: this.#region });
this.logger.log(this.#sessionLog({ name: 'LOGIN', req, instanceID: this.#instanceID, region: this.#region }));
try {
const tenantId = params.tenantId || req.session['tenant'];
const channel = this.options.channelType || params.channelType || req.session['channel'];

const strategy =
this.strategy ||
(this.idpInfos[this.getIdpInfosKey(tenantId, channel)] &&
this.idpInfos[this.getIdpInfosKey(tenantId, channel)].strategy) ||
(await this.createStrategy(tenantId, channel));
// const strategy =
// this.strategy ||
// (this.idpInfos[this.getIdpInfosKey(tenantId, channel)] &&
// this.idpInfos[this.getIdpInfosKey(tenantId, channel)].strategy) ||
// (await this.createStrategy(tenantId, channel));

const prefix = channel && tenantId ? (this.options.channelType ? `/${tenantId}` : `/${tenantId}/${channel}`) : '';

Expand Down Expand Up @@ -166,7 +173,7 @@ export class OidcService implements OnModuleInit {
JSON.stringify({ redirect_url: `${prefix}${redirect_url}`, loginpopup: loginpopup }),
'utf-8',
).toString('base64');
this.#sessionLog({ name: 'PRE_AUTHENTICATE', req, instanceID: this.#instanceID, region: this.#region });
this.logger.log(this.#sessionLog({ name: 'PRE_AUTHENTICATE', req, instanceID: this.#instanceID, region: this.#region }));

passport.authenticate(
Object.create(strategy),
Expand All @@ -178,6 +185,7 @@ export class OidcService implements OnModuleInit {
(err, user, info) => {
if (err || !user) {
this.logger.error(this.#sessionLog({ name: 'AUTHENTICATE ERROR', req, instanceID: this.#instanceID, region: this.#region }));
this.logger.log(`AUTHENTICATE ERROR INFO`, JSON.stringify(info))
return next(err || info);
}
this.logger.log(this.#sessionLog({ name: 'PRE_LOGIN_REQ', req, instanceID: this.#instanceID, region: this.#region }));
Expand All @@ -190,8 +198,6 @@ export class OidcService implements OnModuleInit {
let state = req.query['state'] as string;
const buff = Buffer.from(state, 'base64').toString('utf-8');
state = JSON.parse(buff);
let url: string = state['redirect_url'];
url = !url.startsWith('/') ? `/${url}` : url;
const loginpopup = state['loginpopup'];
this.logger.log(this.#sessionLog({ name: 'PRE_REDIRECT', req, instanceID: this.#instanceID, region: this.#region }));
if (loginpopup) {
Expand All @@ -201,7 +207,12 @@ export class OidcService implements OnModuleInit {
</script >
`);
} else {
this.logger.log(this.#sessionLog({ name: 'REDIRECT', req, instanceID: this.#instanceID, region: this.#region }));
this.logger.log(this.#sessionLog({ name: 'REDIRECT AFTER LOGIN CALLBACK', req, instanceID: this.#instanceID, region: this.#region }));
let url: string = state['redirect_url'];
// TODO: redirecting from region url to app prefix url with callback query params works, but also could lead to infinite loop so solve initial 500 first, then deal with redirect
// let redirect_url = this.#oidc_redirect_uri.replace(`-${this.#region}`, '').replace(_loginCallbackPath, '');
// url = `${redirect_url}/${url}`;
url = !url.startsWith('/') ? `/${url}` : url;
return res.redirect(url);
}
});
Expand All @@ -219,26 +230,20 @@ export class OidcService implements OnModuleInit {
* @param res
* @param next
* @param params
* @returns redirect to other region when session details not found, login method when session params are present
* @returns redirect to other region when session details not found
*/
// async loginCallback(@Req() req: Request, @Res() res: Response, @Next() next: Function, @Param() params) {
// this.logger.log(this.#sessionLog({ name: 'LOGIN CALLBACK', req, instanceID: this.#instanceID, region: this.#region }));

// const tenantId = params.tenantId || req.session['tenant'];
// const channel = this.options.channelType || params.channelType || req.session['channel'];
// const key = this.getIdpInfosKey(tenantId, channel);
// const issuer = this.idpInfos[key].trustIssuer.issuer;
// const sessionKey = `oidc:${url.parse(issuer).hostname}`;
// const session = req.session[sessionKey];

// if (Object.keys(session || {}).length === 0) {
// const url = this.#getRegionToRedirect(this.#region, this.#redirect_uri);
// this.logger.log(this.#sessionLog({ name: 'REGION REDIRECT', req, instanceID: this.#instanceID, region: this.#region }));
// return res.redirect(url);
// } else {
// return this.login(req, res, next, params);
// }
// }
async loginCallback(@Req() req: Request, @Res() res: Response, @Next() next: Function, @Param() params) {
this.logger.log(this.#sessionLog({ name: 'LOGIN CALLBACK', req, instanceID: this.#instanceID, region: this.#region }));
const session = req.session[this.#sessionKey];
if (Object.keys(session || {}).length === 0) {
// const url = this.#getRegionToRedirect(this.#region, this.#oidc_redirect_uri);
this.logger.log(this.#sessionLog({ name: 'SESSION EMPTY', req, instanceID: this.#instanceID, region: this.#region }));
// return res.redirect(url);
}
// } else {
return this.login(req, res, next, params);
// }
}

async logout(@Req() req: Request, @Res() res: Response, @Param() params) {
const id_token = req.user ? req.user['id_token'] : undefined;
Expand Down Expand Up @@ -382,15 +387,14 @@ export class OidcService implements OnModuleInit {
return [tenantPrefix, channelPrefix].filter(Boolean).join('/');
}

// #getRegionToRedirect(region = this.#region, redirectUri = this.#redirect_uri): string {
// const westEU: string = 'westeurope';
// const northEU: string = 'northeurope';
// const redirectRegion = region === westEU ? northEU : westEU;
// #getRegionToRedirect(region = this.#region, redirectUri = this.#oidc_redirect_uri): string {
// const redirectRegion = region === _westEU ? _northEU : _westEU;
// return redirectUri.replace(this.#region, redirectRegion);
// }

#sessionLog({ name, req, instanceID = this.#instanceID, region = this.#region }: LogParams = {} as LogParams): string {
return `${name.toLocaleUpperCase()} url: ${req.url}, session: ${JSON.stringify(req.session)}, ip: ${req.ip}, method: ${req.method}, instanceID: ${instanceID}, region: ${region}`;
const session = req.session[this.#sessionKey] ?? {};
return `${name.toLocaleUpperCase()} url: ${req.url}, oidc_session: ${JSON.stringify(session)}, method: ${req.method}, instanceID: ${instanceID}, region: ${region}`;
}
}

Expand Down
3 changes: 2 additions & 1 deletion libs/oidc/src/strategies/oidc.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ export class OidcStrategy extends PassportStrategy(Strategy, 'oidc') {

userInfoCallback: any;

constructor(private oidcService: OidcService, private idpKey: string, private channelType?: ChannelType) {
constructor(private oidcService: OidcService, private idpKey: string, sessionKey: string, private channelType?: ChannelType) {
super({
client: oidcService.idpInfos[idpKey].client,
params: oidcService.options.authParams,
passReqToCallback: false,
usePKCE: oidcService.options.usePKCE,
sessionKey
});
this.userInfoCallback = oidcService.options.userInfoCallback;
}
Expand Down
4 changes: 4 additions & 0 deletions libs/oidc/src/utils/session/base-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ let session = {
// session.cookie.secure = true; // https only
// }

if (process.env.NODE_ENV !== 'developement') {
session.cookie['domain'] = process.env.DNS_DOMAIN;
}

export const baseSession = session as SessionOptions;

0 comments on commit 7f0ed8e

Please sign in to comment.