Skip to content

Commit 6239f17

Browse files
committed
refactor: pull csrf into a shared module
1 parent a794f51 commit 6239f17

File tree

4 files changed

+39
-39
lines changed

4 files changed

+39
-39
lines changed

lib/actions/code_verification.js

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import * as crypto from 'node:crypto';
2-
31
import sessionMiddleware from '../shared/session.js';
42
import paramsMiddleware from '../shared/assemble_params.js';
53
import bodyParser from '../shared/conditional_body.js';
64
import rejectDupes from '../shared/reject_dupes.js';
75
import instance from '../helpers/weak_cache.js';
8-
import { InvalidClient, InvalidRequest } from '../helpers/errors.js';
6+
import { InvalidClient } from '../helpers/errors.js';
7+
import { generateXsrf, checkXsrf } from '../shared/xsrf.js';
98
import * as formHtml from '../helpers/user_code_form.js';
109
import formPost from '../response_modes/form_post.js';
1110
import { normalize, denormalize } from '../helpers/user_codes.js';
@@ -18,13 +17,11 @@ const parseBody = bodyParser.bind(undefined, 'application/x-www-form-urlencoded'
1817
export const get = [
1918
sessionMiddleware,
2019
paramsMiddleware.bind(undefined, new Set(['user_code'])),
20+
generateXsrf,
2121
async function renderCodeVerification(ctx) {
2222
const { charset, userCodeInputSource } = instance(ctx.oidc.provider).features.deviceFlow;
2323

24-
// TODO: generic xsrf middleware to remove this
25-
const secret = crypto.randomBytes(24).toString('hex');
26-
ctx.oidc.session.state = { secret };
27-
24+
const { secret } = ctx.oidc.session.state;
2825
const action = ctx.oidc.urlFor('code_verification');
2926
if (ctx.oidc.params.user_code) {
3027
formPost(ctx, action, {
@@ -43,15 +40,7 @@ export const post = [
4340
paramsMiddleware.bind(undefined, new Set(['xsrf', 'user_code', 'confirm', 'abort'])),
4441
rejectDupes.bind(undefined, {}),
4542

46-
async function codeVerificationCSRF(ctx, next) {
47-
if (!ctx.oidc.session.state) {
48-
throw new InvalidRequest('could not find device form details');
49-
}
50-
if (ctx.oidc.session.state.secret !== ctx.oidc.params.xsrf) {
51-
throw new InvalidRequest('xsrf token invalid');
52-
}
53-
await next();
54-
},
43+
checkXsrf('could not find device form details'),
5544

5645
async function loadDeviceCodeByUserInput(ctx, next) {
5746
const { userCodeConfirmSource, mask } = instance(ctx.oidc.provider).features.deviceFlow;

lib/actions/end_session.js

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import * as crypto from 'node:crypto';
2-
31
import { InvalidClient, InvalidRequest, OIDCProviderError } from '../helpers/errors.js';
42
import * as JWT from '../helpers/jwt.js';
53
import redirectUri from '../helpers/redirect_uri.js';
@@ -11,6 +9,7 @@ import sessionMiddleware from '../shared/session.js';
119
import revoke from '../helpers/revoke.js';
1210
import noCache from '../shared/no_cache.js';
1311
import formPost from '../response_modes/form_post.js';
12+
import { generateXsrf, checkXsrf } from '../shared/xsrf.js';
1413

1514
const parseBody = bodyParser.bind(undefined, 'application/x-www-form-urlencoded');
1615

@@ -70,16 +69,14 @@ export const init = [
7069
await next();
7170
},
7271

72+
generateXsrf,
73+
7374
async function renderLogout(ctx) {
74-
// TODO: generic xsrf middleware to remove this
75-
const secret = crypto.randomBytes(24).toString('hex');
75+
const { secret } = ctx.oidc.session.state;
7676

77-
ctx.oidc.session.state = {
78-
secret,
79-
clientId: ctx.oidc.client ? ctx.oidc.client.clientId : undefined,
80-
state: ctx.oidc.params.state,
81-
postLogoutRedirectUri: ctx.oidc.params.post_logout_redirect_uri,
82-
};
77+
ctx.oidc.session.state.clientId = ctx.oidc.client ? ctx.oidc.client.clientId : undefined;
78+
ctx.oidc.session.state.state = ctx.oidc.params.state;
79+
ctx.oidc.session.state.postLogoutRedirectUri = ctx.oidc.params.post_logout_redirect_uri;
8380

8481
const action = ctx.oidc.urlFor('end_session_confirm');
8582

@@ -105,15 +102,7 @@ export const confirm = [
105102
paramsMiddleware.bind(undefined, new Set(['xsrf', 'logout'])),
106103
rejectDupes.bind(undefined, {}),
107104

108-
async function checkLogoutToken(ctx, next) {
109-
if (!ctx.oidc.session.state) {
110-
throw new InvalidRequest('could not find logout details');
111-
}
112-
if (ctx.oidc.session.state.secret !== ctx.oidc.params.xsrf) {
113-
throw new InvalidRequest('xsrf token invalid');
114-
}
115-
await next();
116-
},
105+
checkXsrf('could not find logout details'),
117106

118107
async function endSession(ctx) {
119108
const { oidc: { session, params } } = ctx;

lib/shared/error_handler.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import * as crypto from 'node:crypto';
2-
31
import debug from 'debug';
42

53
import instance from '../helpers/weak_cache.js';
64
import * as formHtml from '../helpers/user_code_form.js';
75
import { ReRenderError } from '../helpers/re_render_errors.js';
86
import errOut from '../helpers/err_out.js';
97

8+
import { generateXsrf } from './xsrf.js';
9+
1010
const debugError = debug('oidc-provider:error');
1111
const serverError = debug('oidc-provider:server_error');
1212
const serverErrorTrace = debug('oidc-provider:server_error:trace');
@@ -33,8 +33,8 @@ export default function getErrorHandler(provider, eventName) {
3333
}
3434

3535
if (ctx.oidc?.session && userInputRoutes.has(ctx.oidc.route)) {
36-
const secret = crypto.randomBytes(24).toString('hex');
37-
ctx.oidc.session.state = { secret };
36+
generateXsrf(ctx, () => {});
37+
const { secret } = ctx.oidc.session.state;
3838

3939
await userCodeInputSource(ctx, formHtml.input(ctx.oidc.urlFor('code_verification'), secret, err.userCode, charset), out, err);
4040
if (err instanceof ReRenderError) { // render without emit

lib/shared/xsrf.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import * as crypto from 'node:crypto';
2+
3+
import { InvalidRequest } from '../helpers/errors.js';
4+
import constantEquals from '../helpers/constant_equals.js';
5+
6+
export function generateXsrf(ctx, next) {
7+
const secret = crypto.randomBytes(24).toString('hex');
8+
ctx.oidc.session.state = { secret };
9+
return next();
10+
}
11+
12+
export function checkXsrf(missingMessage) {
13+
return async function verifyXsrf(ctx, next) {
14+
if (!ctx.oidc.session.state) {
15+
throw new InvalidRequest(missingMessage);
16+
}
17+
if (!constantEquals(ctx.oidc.session.state.secret, ctx.oidc.params.xsrf || '')) {
18+
throw new InvalidRequest('xsrf token invalid');
19+
}
20+
await next();
21+
};
22+
}

0 commit comments

Comments
 (0)