Skip to content

Commit b64c34a

Browse files
authored
fix: route user to error page if login is timed out (#465)
* fix: route user to error page if login is timed out * fix: use 408 for timeout
1 parent 20ec16d commit b64c34a

File tree

6 files changed

+48
-11
lines changed

6 files changed

+48
-11
lines changed

docker/otp-provider/src/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,6 @@ export const config = {
2525
OTP_VALIDITY_MINUTES: process.env.OTP_VALIDITY_MINUTES || '5',
2626
OTP_ATTEMPTS_ALLOWED: process.env.OTP_ATTEMPTS_ALLOWED || '5',
2727
OTP_RESENDS_ALLOWED_PER_DAY: process.env.OTP_RESENDS_ALLOWED_PER_DAY || '4',
28-
OTP_RESEND_INTERVAL_MINUTES: process.env.OTP_RESEND_INTERVAL_MINUTES || '[1,2,5,60]',
28+
OTP_RESEND_INTERVAL_MINUTES: process.env.OTP_RESEND_INTERVAL_MINUTES || '[1,2,5,25]',
2929
COOKIE_SECRETS: process.env.COOKIE_SECRETS || 's3cr3t1,s3cr3t1,s3cr3t2',
3030
};

docker/otp-provider/src/controllers/auth-controller.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { requestNewOtp, validateOtp } from '../services/otp';
44
import { canRequestOtp, secondsRemainingToRequestNewOtp } from '../utils/otp';
55
import { emailValidator, otpValidator } from '../utils/shared';
66
import { errors } from '../modules/errors';
7+
import { getInteractionById } from '../modules/sequelize/queries/interaction';
8+
import { LoginTimeoutError } from '../utils/helpers';
79

810
export const authorize = async (oidcProvider: Provider) => {
911
return async (req: Request, res: Response, next: NextFunction) => {
@@ -35,6 +37,8 @@ export const authorize = async (oidcProvider: Provider) => {
3537
export const generateOtp = async (oidcProvider: Provider) => {
3638
return async (req: Request, res: Response, next: NextFunction) => {
3739
try {
40+
if (req.params?.uid && (await isInteractionSessionExpired(String(req.params?.uid))))
41+
throw new LoginTimeoutError();
3842
const {
3943
uid,
4044
prompt: { name },
@@ -54,7 +58,7 @@ export const generateOtp = async (oidcProvider: Provider) => {
5458
error,
5559
nonce: res.locals.cspNonce,
5660
waitTime: 0,
57-
})
61+
});
5862
}
5963

6064
if (!error) {
@@ -99,7 +103,6 @@ export const generateOtp = async (oidcProvider: Provider) => {
99103
},
100104
} as any);
101105

102-
103106
return res.render('otp', {
104107
uid,
105108
email,
@@ -119,6 +122,9 @@ export const generateOtp = async (oidcProvider: Provider) => {
119122
export const login = async (oidcProvider: Provider) => {
120123
return async (req: Request, res: Response, next: NextFunction) => {
121124
try {
125+
if (req.params?.uid && (await isInteractionSessionExpired(String(req.params?.uid))))
126+
throw new LoginTimeoutError();
127+
122128
const {
123129
uid,
124130
prompt: { name },
@@ -131,20 +137,21 @@ export const login = async (oidcProvider: Provider) => {
131137

132138
if (name === 'login') {
133139
let validatedOtp = { verified: false, attemptsLeft: 0, expired: false };
134-
const { code1, code2, code3, code4, code5, code6, } = req.body;
140+
const { code1, code2, code3, code4, code5, code6 } = req.body;
135141
const email = (oidcResult?.login?.email as string) || '';
136142

137143
// Run form validation server side
138144
const [otp, otpError] = otpValidator([code1, code2, code3, code4, code5, code6]);
139-
if (otpError) return res.render('otp', {
145+
if (otpError)
146+
return res.render('otp', {
140147
uid,
141148
email,
142149
nonce: res.locals.cspNonce,
143150
waitTime: time,
144151
disableResend: false,
145152
disableForm: false,
146-
error: otpError
147-
});
153+
error: otpError,
154+
});
148155

149156
let disableResend = false;
150157
validatedOtp = await validateOtp(otp as string, email);
@@ -210,3 +217,9 @@ export const abortLogin = async (oidcProvider: Provider) => {
210217
}
211218
};
212219
};
220+
221+
const isInteractionSessionExpired = async (interactionUid: string) => {
222+
const interaction = await getInteractionById(interactionUid);
223+
if (interaction && new Date().getTime() >= new Date(interaction.expiresAt).getTime()) return true;
224+
return false;
225+
};

docker/otp-provider/src/modules/oidc-provider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,8 @@ export const getConfig = (): Configuration => {
121121
Session: 36000, // 10 hours
122122
AccessToken: 300, // 5 minutes
123123
AuthorizationCode: 60, // 1 minute
124-
RefreshToken: 1800, // 2 seconds
125-
Interaction: 36000, // 10 hours
124+
RefreshToken: 1800, // 30 minutes
125+
Interaction: 1800, // 30 minutes
126126
IdToken: 300, // 5 minutes
127127
//Grant controls how long the authorization grant (which includes tokens and scopes) is valid. This affects token reuse and refresh behavior.
128128
Grant: 36000, // 10 hours - client session max
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { QueryOptions } from 'sequelize';
2+
import models from '../models';
3+
4+
export const getInteractionById = async (id: string, options: QueryOptions = { raw: true }) => {
5+
return await models.get('Interaction').findOne({
6+
where: { id },
7+
...options,
8+
});
9+
};

docker/otp-provider/src/routes/interaction.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import express, { NextFunction, Request, Response, urlencoded } from 'express';
22
import Provider from 'oidc-provider';
33
import { authorize, generateOtp, login, abortLogin } from '../controllers/auth-controller';
4-
import { setNoCache } from '../utils/helpers';
4+
import { LoginTimeoutError, setNoCache } from '../utils/helpers';
55
import { errors } from 'oidc-provider';
66
import logger from '../modules/winston.config';
77

@@ -18,7 +18,10 @@ export const oidcRouter = async (oidcProvider: Provider) => {
1818
logger.error('OIDC interaction error:', err);
1919
let errorMessage = 'An unexpected error occurred';
2020
let errorStatus = 500;
21-
if (err instanceof errors.InvalidRequest) {
21+
if (err instanceof LoginTimeoutError) {
22+
errorStatus = err.status || 408;
23+
errorMessage = err.message;
24+
} else if (err instanceof errors.InvalidRequest) {
2225
errorMessage = 'Invalid request parameters sent.';
2326
errorStatus = err.status || 400;
2427
} else if (err instanceof errors.InvalidGrant) {

docker/otp-provider/src/utils/helpers.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,15 @@ export const setNoCache = (req: Request, res: Response, next: NextFunction) => {
2727
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
2828
next();
2929
};
30+
31+
export class LoginTimeoutError extends Error {
32+
status: number;
33+
constructor(
34+
message = 'Your session has timed out, please close this window and log in again using a new browser window',
35+
) {
36+
super(message);
37+
this.name = 'LoginTimeoutError';
38+
this.status = 408;
39+
Object.setPrototypeOf(this, LoginTimeoutError.prototype);
40+
}
41+
}

0 commit comments

Comments
 (0)