@wbce-d9/api 12.3.0 → 12.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/drivers/baseoauth.d.ts +3 -3
- package/dist/auth/drivers/baseoauth.js +7 -7
- package/dist/auth/drivers/oauth2.d.ts +5 -5
- package/dist/auth/drivers/oauth2.js +25 -24
- package/dist/auth/drivers/openid.d.ts +5 -5
- package/dist/auth/drivers/openid.js +41 -48
- package/dist/constants.js +1 -1
- package/dist/env.js +5 -0
- package/dist/exceptions/forbidden.js +1 -1
- package/dist/utils/generate-hash.js +1 -1
- package/package.json +15 -15
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as oidc from 'openid-client';
|
|
2
2
|
import { UsersService } from '../../services/users.js';
|
|
3
3
|
import type { User } from '../../types/index.js';
|
|
4
4
|
import { LocalAuthDriver } from './local.js';
|
|
@@ -12,12 +12,12 @@ export interface UserPayload {
|
|
|
12
12
|
auth_data: any;
|
|
13
13
|
}
|
|
14
14
|
export declare abstract class BaseOAuthDriver extends LocalAuthDriver {
|
|
15
|
-
abstract getClient(): Promise<
|
|
15
|
+
abstract getClient(): Promise<oidc.Configuration>;
|
|
16
16
|
abstract getredirectUrl(): string;
|
|
17
17
|
abstract getUserService(): UsersService;
|
|
18
18
|
abstract getConfig(): Record<string, any>;
|
|
19
19
|
abstract getClientName(): string;
|
|
20
|
-
abstract getTokenSetAndUserInfo(payload: Record<string, any>): Promise<[
|
|
20
|
+
abstract getTokenSetAndUserInfo(payload: Record<string, any>): Promise<[oidc.TokenEndpointResponse & oidc.TokenEndpointResponseHelpers, Record<string, unknown>, UserPayload]>;
|
|
21
21
|
generateCodeVerifier(): string;
|
|
22
22
|
fetchUserId(identifier: string): Promise<string | undefined>;
|
|
23
23
|
getUserID(payload: Record<string, any>): Promise<string>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parseJSON } from '@wbce-d9/utils';
|
|
2
|
-
import
|
|
2
|
+
import * as oidc from 'openid-client';
|
|
3
3
|
import getDatabase from '../../database/index.js';
|
|
4
4
|
import emitter from '../../emitter.js';
|
|
5
5
|
import { RecordNotUniqueException } from '../../exceptions/database/record-not-unique.js';
|
|
@@ -9,7 +9,7 @@ import { UsersService } from '../../services/users.js';
|
|
|
9
9
|
import { LocalAuthDriver } from './local.js';
|
|
10
10
|
export class BaseOAuthDriver extends LocalAuthDriver {
|
|
11
11
|
generateCodeVerifier() {
|
|
12
|
-
return
|
|
12
|
+
return oidc.randomPKCECodeVerifier();
|
|
13
13
|
}
|
|
14
14
|
async fetchUserId(identifier) {
|
|
15
15
|
const user = await this.knex.select('id').from('directus_users').where('external_identifier', identifier).first();
|
|
@@ -86,9 +86,9 @@ export class BaseOAuthDriver extends LocalAuthDriver {
|
|
|
86
86
|
if (authData?.['refreshToken']) {
|
|
87
87
|
try {
|
|
88
88
|
const client = await this.getClient();
|
|
89
|
-
const tokenSet = await
|
|
89
|
+
const tokenSet = await oidc.refreshTokenGrant(client, authData['refreshToken']);
|
|
90
90
|
if (tokenSet.refresh_token) {
|
|
91
|
-
await
|
|
91
|
+
await oidc.tokenRevocation(client, tokenSet.refresh_token);
|
|
92
92
|
}
|
|
93
93
|
await this.getUserService().updateOne(user.id, {
|
|
94
94
|
auth_data: null,
|
|
@@ -119,7 +119,7 @@ export class BaseOAuthDriver extends LocalAuthDriver {
|
|
|
119
119
|
if (authData?.['refreshToken']) {
|
|
120
120
|
try {
|
|
121
121
|
const client = await this.getClient();
|
|
122
|
-
const tokenSet = await
|
|
122
|
+
const tokenSet = await oidc.refreshTokenGrant(client, authData['refreshToken']);
|
|
123
123
|
// Update user refreshToken if provided
|
|
124
124
|
if (tokenSet.refresh_token) {
|
|
125
125
|
await this.getUserService().updateOne(user.id, {
|
|
@@ -138,7 +138,7 @@ export class BaseOAuthDriver extends LocalAuthDriver {
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
handleError = (e) => {
|
|
141
|
-
if (e instanceof
|
|
141
|
+
if (e instanceof oidc.ResponseBodyError) {
|
|
142
142
|
if (e.error === 'invalid_grant') {
|
|
143
143
|
// Invalid token
|
|
144
144
|
logger.trace(e, `[${this.getClientName()}] Invalid grant`);
|
|
@@ -151,7 +151,7 @@ export class BaseOAuthDriver extends LocalAuthDriver {
|
|
|
151
151
|
message: e.error_description,
|
|
152
152
|
});
|
|
153
153
|
}
|
|
154
|
-
else if (e instanceof
|
|
154
|
+
else if (e instanceof oidc.ClientError) {
|
|
155
155
|
// Internal client error
|
|
156
156
|
logger.trace(e, `[${this.getClientName()}] Unknown RP error`);
|
|
157
157
|
return new InvalidCredentialsException();
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
|
-
import
|
|
2
|
+
import * as oidc from 'openid-client';
|
|
3
3
|
import { UsersService } from '../../services/users.js';
|
|
4
4
|
import type { AuthDriverOptions } from '../../types/index.js';
|
|
5
5
|
import { BaseOAuthDriver, type UserPayload } from './baseoauth.js';
|
|
6
6
|
export declare class OAuth2AuthDriver extends BaseOAuthDriver {
|
|
7
|
-
client:
|
|
7
|
+
client: oidc.Configuration;
|
|
8
8
|
redirectUrl: string;
|
|
9
9
|
usersService: UsersService;
|
|
10
10
|
config: Record<string, any>;
|
|
11
|
-
getClient(): Promise<
|
|
11
|
+
getClient(): Promise<oidc.Configuration>;
|
|
12
12
|
getredirectUrl(): string;
|
|
13
13
|
getUserService(): UsersService;
|
|
14
14
|
getConfig(): Record<string, any>;
|
|
15
15
|
getClientName(): string;
|
|
16
16
|
constructor(options: AuthDriverOptions, config: Record<string, any>);
|
|
17
|
-
generateAuthUrl(codeVerifier: string, prompt?: boolean): string
|
|
18
|
-
getTokenSetAndUserInfo(payload: Record<string, any>): Promise<[
|
|
17
|
+
generateAuthUrl(codeVerifier: string, prompt?: boolean): Promise<string>;
|
|
18
|
+
getTokenSetAndUserInfo(payload: Record<string, any>): Promise<[oidc.TokenEndpointResponse & oidc.TokenEndpointResponseHelpers, Record<string, unknown>, UserPayload]>;
|
|
19
19
|
}
|
|
20
20
|
export declare function createOAuth2AuthRouter(providerName: string): Router;
|
|
@@ -2,9 +2,9 @@ import { BaseException } from '@wbce-d9/exceptions';
|
|
|
2
2
|
import express, { Router } from 'express';
|
|
3
3
|
import flatten from 'flat';
|
|
4
4
|
import jwt from 'jsonwebtoken';
|
|
5
|
-
import
|
|
5
|
+
import * as oidc from 'openid-client';
|
|
6
6
|
import { getAuthProvider } from '../../auth.js';
|
|
7
|
-
import { ACCESS_COOKIE_OPTIONS,
|
|
7
|
+
import { ACCESS_COOKIE_OPTIONS, OAUTH2_COOKIE_OPTIONS, REFRESH_COOKIE_OPTIONS } from '../../constants.js';
|
|
8
8
|
import env from '../../env.js';
|
|
9
9
|
import { InvalidConfigException, InvalidCredentialsException, InvalidTokenException, InvalidPayloadException, } from '../../exceptions/index.js';
|
|
10
10
|
import logger from '../../logger.js';
|
|
@@ -47,35 +47,31 @@ export class OAuth2AuthDriver extends BaseOAuthDriver {
|
|
|
47
47
|
this.redirectUrl = redirectUrl.toString();
|
|
48
48
|
this.usersService = new UsersService({ knex: this.knex, schema: this.schema });
|
|
49
49
|
this.config = additionalConfig;
|
|
50
|
-
const
|
|
50
|
+
const clientOptionsOverrides = getConfigFromEnv(`AUTH_${config['provider'].toUpperCase()}_CLIENT_`, [`AUTH_${config['provider'].toUpperCase()}_CLIENT_ID`, `AUTH_${config['provider'].toUpperCase()}_CLIENT_SECRET`], 'underscore');
|
|
51
|
+
this.client = new oidc.Configuration({
|
|
51
52
|
authorization_endpoint: authorizeUrl,
|
|
52
53
|
token_endpoint: accessUrl,
|
|
53
54
|
userinfo_endpoint: profileUrl,
|
|
54
55
|
issuer: additionalConfig['provider'],
|
|
55
|
-
});
|
|
56
|
-
const clientOptionsOverrides = getConfigFromEnv(`AUTH_${config['provider'].toUpperCase()}_CLIENT_`, [`AUTH_${config['provider'].toUpperCase()}_CLIENT_ID`, `AUTH_${config['provider'].toUpperCase()}_CLIENT_SECRET`], 'underscore');
|
|
57
|
-
this.client = new issuer.Client({
|
|
58
|
-
client_id: clientId,
|
|
59
|
-
client_secret: clientSecret,
|
|
60
|
-
redirect_uris: [this.redirectUrl],
|
|
61
|
-
response_types: ['code'],
|
|
62
|
-
...clientOptionsOverrides,
|
|
63
|
-
});
|
|
56
|
+
}, clientId, { client_secret: clientSecret, ...clientOptionsOverrides });
|
|
64
57
|
}
|
|
65
|
-
generateAuthUrl(codeVerifier, prompt = false) {
|
|
58
|
+
async generateAuthUrl(codeVerifier, prompt = false) {
|
|
66
59
|
try {
|
|
67
|
-
const codeChallenge =
|
|
60
|
+
const codeChallenge = await oidc.calculatePKCECodeChallenge(codeVerifier);
|
|
68
61
|
const paramsConfig = typeof this.config['params'] === 'object' ? this.config['params'] : {};
|
|
69
|
-
|
|
62
|
+
const params = {
|
|
70
63
|
scope: this.config['scope'] ?? 'email',
|
|
71
64
|
access_type: 'offline',
|
|
72
|
-
prompt: prompt ? 'consent' : undefined,
|
|
73
65
|
...paramsConfig,
|
|
74
66
|
code_challenge: codeChallenge,
|
|
75
67
|
code_challenge_method: 'S256',
|
|
76
68
|
// Some providers require state even with PKCE
|
|
77
69
|
state: codeChallenge,
|
|
78
|
-
}
|
|
70
|
+
};
|
|
71
|
+
if (prompt) {
|
|
72
|
+
params['prompt'] = 'consent';
|
|
73
|
+
}
|
|
74
|
+
return oidc.buildAuthorizationUrl(this.client, params).href;
|
|
79
75
|
}
|
|
80
76
|
catch (e) {
|
|
81
77
|
throw this.handleError(e);
|
|
@@ -85,8 +81,13 @@ export class OAuth2AuthDriver extends BaseOAuthDriver {
|
|
|
85
81
|
let tokenSet;
|
|
86
82
|
let userInfo;
|
|
87
83
|
try {
|
|
88
|
-
|
|
89
|
-
|
|
84
|
+
const codeChallenge = await oidc.calculatePKCECodeChallenge(payload['codeVerifier']);
|
|
85
|
+
const callbackUrl = new URL(payload['callbackPath'], this.redirectUrl);
|
|
86
|
+
tokenSet = await oidc.authorizationCodeGrant(this.client, callbackUrl, {
|
|
87
|
+
pkceCodeVerifier: payload['codeVerifier'],
|
|
88
|
+
expectedState: codeChallenge,
|
|
89
|
+
});
|
|
90
|
+
userInfo = await oidc.fetchUserInfo(this.client, tokenSet.access_token, oidc.skipSubjectCheck);
|
|
90
91
|
}
|
|
91
92
|
catch (e) {
|
|
92
93
|
throw this.handleError(e);
|
|
@@ -115,7 +116,7 @@ export class OAuth2AuthDriver extends BaseOAuthDriver {
|
|
|
115
116
|
}
|
|
116
117
|
export function createOAuth2AuthRouter(providerName) {
|
|
117
118
|
const router = Router();
|
|
118
|
-
router.get('/', (req, res) => {
|
|
119
|
+
router.get('/', asyncHandler(async (req, res) => {
|
|
119
120
|
const provider = getAuthProvider(providerName);
|
|
120
121
|
const codeVerifier = provider.generateCodeVerifier();
|
|
121
122
|
const prompt = !!req.query['prompt'];
|
|
@@ -124,12 +125,12 @@ export function createOAuth2AuthRouter(providerName) {
|
|
|
124
125
|
throw new InvalidPayloadException(`URL "${redirect}" can't be used to redirect after login`);
|
|
125
126
|
}
|
|
126
127
|
const token = jwt.sign({ verifier: codeVerifier, redirect, prompt }, env['SECRET'], {
|
|
127
|
-
expiresIn:
|
|
128
|
+
expiresIn: Math.floor(OAUTH2_COOKIE_OPTIONS.maxAge / 1000),
|
|
128
129
|
issuer: 'directus',
|
|
129
130
|
});
|
|
130
131
|
res.cookie(`oauth2.${providerName}`, token, OAUTH2_COOKIE_OPTIONS);
|
|
131
|
-
return res.redirect(provider.generateAuthUrl(codeVerifier, prompt));
|
|
132
|
-
}, respond);
|
|
132
|
+
return res.redirect(await provider.generateAuthUrl(codeVerifier, prompt));
|
|
133
|
+
}), respond);
|
|
133
134
|
router.post('/callback', express.urlencoded({ extended: false }), (req, res) => {
|
|
134
135
|
res.redirect(303, `./callback?${new URLSearchParams(req.body)}`);
|
|
135
136
|
}, respond);
|
|
@@ -161,8 +162,8 @@ export function createOAuth2AuthRouter(providerName) {
|
|
|
161
162
|
});
|
|
162
163
|
let authResponse;
|
|
163
164
|
try {
|
|
164
|
-
res.clearCookie(`oauth2.${providerName}`, OAUTH2_COOKIE_CLEAR_OPTIONS);
|
|
165
165
|
authResponse = await authenticationService.login(providerName, {
|
|
166
|
+
callbackPath: req.originalUrl,
|
|
166
167
|
code: req.query['code'],
|
|
167
168
|
codeVerifier: verifier,
|
|
168
169
|
state: req.query['state'],
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
|
-
import
|
|
2
|
+
import * as oidc from 'openid-client';
|
|
3
3
|
import { UsersService } from '../../services/users.js';
|
|
4
4
|
import type { AuthDriverOptions } from '../../types/index.js';
|
|
5
5
|
import { BaseOAuthDriver, type UserPayload } from './baseoauth.js';
|
|
6
6
|
export declare class OpenIDAuthDriver extends BaseOAuthDriver {
|
|
7
|
-
client: Promise<
|
|
7
|
+
client: Promise<oidc.Configuration>;
|
|
8
8
|
redirectUrl: string;
|
|
9
9
|
usersService: UsersService;
|
|
10
10
|
config: Record<string, any>;
|
|
11
|
-
getClient(): Promise<
|
|
11
|
+
getClient(): Promise<oidc.Configuration>;
|
|
12
12
|
getredirectUrl(): string;
|
|
13
13
|
getUserService(): UsersService;
|
|
14
14
|
getConfig(): Record<string, any>;
|
|
15
15
|
getClientName(): string;
|
|
16
16
|
constructor(options: AuthDriverOptions, config: Record<string, any>);
|
|
17
|
-
generateAuthUrl(codeVerifier: string, prompt?: boolean, additionalParams?:
|
|
18
|
-
getTokenSetAndUserInfo(payload: Record<string, any>): Promise<[
|
|
17
|
+
generateAuthUrl(codeVerifier: string, prompt?: boolean, additionalParams?: Record<string, string>): Promise<string>;
|
|
18
|
+
getTokenSetAndUserInfo(payload: Record<string, any>): Promise<[oidc.TokenEndpointResponse & oidc.TokenEndpointResponseHelpers, Record<string, unknown>, UserPayload]>;
|
|
19
19
|
}
|
|
20
20
|
export declare function createOpenIDAuthRouter(providerName: string): Router;
|
|
@@ -2,9 +2,9 @@ import { BaseException } from '@wbce-d9/exceptions';
|
|
|
2
2
|
import express, { Router } from 'express';
|
|
3
3
|
import flatten from 'flat';
|
|
4
4
|
import jwt from 'jsonwebtoken';
|
|
5
|
-
import
|
|
5
|
+
import * as oidc from 'openid-client';
|
|
6
6
|
import { getAuthProvider } from '../../auth.js';
|
|
7
|
-
import { ACCESS_COOKIE_OPTIONS,
|
|
7
|
+
import { ACCESS_COOKIE_OPTIONS, OAUTH2_COOKIE_OPTIONS, REFRESH_COOKIE_OPTIONS } from '../../constants.js';
|
|
8
8
|
import env from '../../env.js';
|
|
9
9
|
import { InvalidConfigException, InvalidCredentialsException, InvalidTokenException, InvalidPayloadException, } from '../../exceptions/index.js';
|
|
10
10
|
import logger from '../../logger.js';
|
|
@@ -48,55 +48,48 @@ export class OpenIDAuthDriver extends BaseOAuthDriver {
|
|
|
48
48
|
this.redirectUrl = redirectUrl.toString();
|
|
49
49
|
this.usersService = new UsersService({ knex: this.knex, schema: this.schema });
|
|
50
50
|
this.config = additionalConfig;
|
|
51
|
-
this.client =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (!supportedTypes?.includes('code')) {
|
|
56
|
-
reject(new InvalidConfigException('OpenID provider does not support required code flow', {
|
|
57
|
-
provider: additionalConfig['provider'],
|
|
58
|
-
}));
|
|
59
|
-
}
|
|
60
|
-
resolve(new issuer.Client({
|
|
61
|
-
client_id: clientId,
|
|
51
|
+
this.client = (async () => {
|
|
52
|
+
let config;
|
|
53
|
+
try {
|
|
54
|
+
config = await oidc.discovery(new URL(issuerUrl), clientId, {
|
|
62
55
|
client_secret: clientSecret,
|
|
63
|
-
redirect_uris: [this.redirectUrl],
|
|
64
|
-
response_types: ['code'],
|
|
65
56
|
...clientOptionsOverrides,
|
|
66
|
-
})
|
|
67
|
-
}
|
|
68
|
-
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
69
60
|
logger.error(e, '[OpenID] Failed to fetch provider config');
|
|
70
61
|
process.exit(1);
|
|
71
|
-
}
|
|
72
|
-
|
|
62
|
+
}
|
|
63
|
+
const supportedTypes = config.serverMetadata().response_types_supported;
|
|
64
|
+
if (supportedTypes && !supportedTypes.includes('code')) {
|
|
65
|
+
throw new InvalidConfigException('OpenID provider does not support required code flow', {
|
|
66
|
+
provider: additionalConfig['provider'],
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return config;
|
|
70
|
+
})();
|
|
73
71
|
}
|
|
74
72
|
async generateAuthUrl(codeVerifier, prompt = false, additionalParams) {
|
|
75
73
|
try {
|
|
76
74
|
const client = await this.client;
|
|
77
|
-
const codeChallenge =
|
|
75
|
+
const codeChallenge = await oidc.calculatePKCECodeChallenge(codeVerifier);
|
|
78
76
|
const paramsConfig = typeof this.config['params'] === 'object' ? this.config['params'] : {};
|
|
79
|
-
const
|
|
80
|
-
delete additionalParams?.['redirect'];
|
|
81
|
-
let finalRedirectUri = this.redirectUrl;
|
|
82
|
-
if (redirectAfterLogin) {
|
|
83
|
-
const redirectUriWithParams = new URL(this.redirectUrl);
|
|
84
|
-
redirectUriWithParams.searchParams.set('redirect', redirectAfterLogin);
|
|
85
|
-
finalRedirectUri = redirectUriWithParams.toString();
|
|
86
|
-
}
|
|
87
|
-
return client.authorizationUrl({
|
|
77
|
+
const params = {
|
|
88
78
|
scope: this.config['scope'] ?? 'openid profile email',
|
|
89
79
|
access_type: 'offline',
|
|
90
|
-
prompt: prompt ? 'consent' : undefined,
|
|
91
80
|
...paramsConfig,
|
|
92
81
|
code_challenge: codeChallenge,
|
|
93
82
|
code_challenge_method: 'S256',
|
|
94
83
|
// Some providers require state even with PKCE
|
|
95
84
|
state: codeChallenge,
|
|
96
85
|
nonce: codeChallenge,
|
|
97
|
-
redirect_uri:
|
|
86
|
+
redirect_uri: this.redirectUrl,
|
|
98
87
|
...additionalParams,
|
|
99
|
-
}
|
|
88
|
+
};
|
|
89
|
+
if (prompt) {
|
|
90
|
+
params.prompt = 'consent';
|
|
91
|
+
}
|
|
92
|
+
return oidc.buildAuthorizationUrl(client, params).href;
|
|
100
93
|
}
|
|
101
94
|
catch (e) {
|
|
102
95
|
throw this.handleError(e);
|
|
@@ -107,19 +100,18 @@ export class OpenIDAuthDriver extends BaseOAuthDriver {
|
|
|
107
100
|
let userInfo;
|
|
108
101
|
try {
|
|
109
102
|
const client = await this.client;
|
|
110
|
-
const codeChallenge =
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (client.issuer.metadata['userinfo_endpoint']) {
|
|
103
|
+
const codeChallenge = await oidc.calculatePKCECodeChallenge(payload['codeVerifier']);
|
|
104
|
+
const callbackUrl = new URL(payload['callbackPath'], this.redirectUrl);
|
|
105
|
+
tokenSet = await oidc.authorizationCodeGrant(client, callbackUrl, {
|
|
106
|
+
pkceCodeVerifier: payload['codeVerifier'],
|
|
107
|
+
expectedState: codeChallenge,
|
|
108
|
+
expectedNonce: codeChallenge,
|
|
109
|
+
});
|
|
110
|
+
userInfo = (tokenSet.claims() ?? {});
|
|
111
|
+
if (client.serverMetadata().userinfo_endpoint) {
|
|
120
112
|
userInfo = {
|
|
121
113
|
...userInfo,
|
|
122
|
-
...(await
|
|
114
|
+
...(await oidc.fetchUserInfo(client, tokenSet.access_token, tokenSet.claims()?.sub ?? oidc.skipSubjectCheck)),
|
|
123
115
|
};
|
|
124
116
|
}
|
|
125
117
|
}
|
|
@@ -159,11 +151,12 @@ export function createOpenIDAuthRouter(providerName) {
|
|
|
159
151
|
throw new InvalidPayloadException(`URL "${redirect}" can't be used to redirect after login`);
|
|
160
152
|
}
|
|
161
153
|
const token = jwt.sign({ verifier: codeVerifier, redirect, prompt }, env['SECRET'], {
|
|
162
|
-
expiresIn:
|
|
154
|
+
expiresIn: Math.floor(OAUTH2_COOKIE_OPTIONS.maxAge / 1000),
|
|
163
155
|
issuer: 'directus',
|
|
164
156
|
});
|
|
165
|
-
const additionalParams =
|
|
166
|
-
|
|
157
|
+
const additionalParams = Object.fromEntries(Object.entries(req.query)
|
|
158
|
+
.filter(([k, v]) => k !== 'prompt' && k !== 'redirect' && typeof v === 'string')
|
|
159
|
+
.map(([k, v]) => [k, v]));
|
|
167
160
|
const authUrl = await provider.generateAuthUrl(codeVerifier, prompt, additionalParams);
|
|
168
161
|
const urlParams = new URL(authUrl);
|
|
169
162
|
const state = urlParams.searchParams.get('state');
|
|
@@ -211,13 +204,13 @@ export function createOpenIDAuthRouter(providerName) {
|
|
|
211
204
|
});
|
|
212
205
|
let authResponse;
|
|
213
206
|
try {
|
|
214
|
-
res.clearCookie(cookieName, OAUTH2_COOKIE_CLEAR_OPTIONS);
|
|
215
207
|
authResponse = await authenticationService.login(providerName, {
|
|
216
208
|
code: req.query['code'],
|
|
217
209
|
codeVerifier: verifier,
|
|
218
210
|
state: req.query['state'],
|
|
219
211
|
iss: req.query['iss'],
|
|
220
212
|
redirect: validRedirectUrl,
|
|
213
|
+
callbackPath: req.originalUrl,
|
|
221
214
|
});
|
|
222
215
|
}
|
|
223
216
|
catch (error) {
|
package/dist/constants.js
CHANGED
|
@@ -85,7 +85,7 @@ export const ACCESS_COOKIE_CLEAR_OPTIONS = (() => {
|
|
|
85
85
|
export const OAUTH2_COOKIE_OPTIONS = {
|
|
86
86
|
httpOnly: true,
|
|
87
87
|
domain: env['OAUTH2_TOKEN_COOKIE_DOMAIN'] || env['ACCESS_TOKEN_COOKIE_DOMAIN'],
|
|
88
|
-
maxAge: getMilliseconds(env['OAUTH2_TOKEN_TTL']
|
|
88
|
+
maxAge: getMilliseconds(env['OAUTH2_TOKEN_TTL']),
|
|
89
89
|
secure: (env['OAUTH2_TOKEN_COOKIE_SECURE'] || env['ACCESS_TOKEN_COOKIE_SECURE']) ?? false,
|
|
90
90
|
sameSite: env['OAUTH2_TOKEN_COOKIE_SAME_SITE'] || 'lax',
|
|
91
91
|
path: '/auth',
|
package/dist/env.js
CHANGED
|
@@ -49,6 +49,10 @@ const allowedEnvironmentVars = [
|
|
|
49
49
|
'REFRESH_TOKEN_COOKIE_SAME_SITE',
|
|
50
50
|
'REFRESH_TOKEN_COOKIE_NAME',
|
|
51
51
|
'REFRESH_TOKEN_OVERLAP_DURATION',
|
|
52
|
+
'OAUTH2_TOKEN_TTL',
|
|
53
|
+
'OAUTH2_TOKEN_COOKIE_DOMAIN',
|
|
54
|
+
'OAUTH2_TOKEN_COOKIE_SECURE',
|
|
55
|
+
'OAUTH2_TOKEN_COOKIE_SAME_SITE',
|
|
52
56
|
'LOGIN_STALL_TIME',
|
|
53
57
|
'PASSWORD_RESET_URL_ALLOW_LIST',
|
|
54
58
|
'USER_INVITE_URL_ALLOW_LIST',
|
|
@@ -235,6 +239,7 @@ const defaults = {
|
|
|
235
239
|
REFRESH_TOKEN_COOKIE_SAME_SITE: 'lax',
|
|
236
240
|
REFRESH_TOKEN_COOKIE_NAME: 'directus_refresh_token',
|
|
237
241
|
REFRESH_TOKEN_OVERLAP_DURATION: '10s',
|
|
242
|
+
OAUTH2_TOKEN_TTL: '1d',
|
|
238
243
|
LOGIN_STALL_TIME: 500,
|
|
239
244
|
SERVER_SHUTDOWN_TIMEOUT: 1000,
|
|
240
245
|
ROOT_REDIRECT: './admin',
|
|
@@ -7,7 +7,7 @@ export class ForbiddenException extends BaseException {
|
|
|
7
7
|
* We currently don't show the reason for a forbidden exception in the API output, as that
|
|
8
8
|
* has the potential to leak schema information (eg a "No permission" vs "No permission to files"
|
|
9
9
|
* would leak that a thing called "files" exists.
|
|
10
|
-
* Ref https://github.com/LaWebcapsule/
|
|
10
|
+
* Ref https://github.com/LaWebcapsule/d9/discussions/4368
|
|
11
11
|
*/
|
|
12
12
|
}
|
|
13
13
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import argon2 from 'argon2';
|
|
2
2
|
import { getConfigFromEnv } from './get-config-from-env.js';
|
|
3
3
|
export function generateHash(stringToHash) {
|
|
4
|
-
const argon2HashConfigOptions = getConfigFromEnv('HASH_', 'HASH_RAW'); // Disallow the HASH_RAW option, see https://github.com/LaWebcapsule/
|
|
4
|
+
const argon2HashConfigOptions = getConfigFromEnv('HASH_', 'HASH_RAW'); // Disallow the HASH_RAW option, see https://github.com/LaWebcapsule/d9/discussions/7670#discussioncomment-1255805
|
|
5
5
|
// associatedData, if specified, must be passed as a Buffer to argon2.hash, see https://github.com/ranisalt/node-argon2/wiki/Options#associateddata
|
|
6
6
|
'associatedData' in argon2HashConfigOptions &&
|
|
7
7
|
(argon2HashConfigOptions['associatedData'] = Buffer.from(argon2HashConfigOptions['associatedData']));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wbce-d9/api",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.4.2",
|
|
4
4
|
"description": "Directus is a real-time API and App dashboard for managing SQL database content",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -22,16 +22,16 @@
|
|
|
22
22
|
"framework",
|
|
23
23
|
"vue"
|
|
24
24
|
],
|
|
25
|
-
"homepage": "https://github.com/LaWebcapsule/
|
|
25
|
+
"homepage": "https://github.com/LaWebcapsule/d9",
|
|
26
26
|
"bugs": {
|
|
27
|
-
"url": "https://github.com/LaWebcapsule/
|
|
27
|
+
"url": "https://github.com/LaWebcapsule/d9/issues"
|
|
28
28
|
},
|
|
29
29
|
"repository": {
|
|
30
30
|
"type": "git",
|
|
31
|
-
"url": "https://github.com/LaWebcapsule/
|
|
31
|
+
"url": "https://github.com/LaWebcapsule/d9.git",
|
|
32
32
|
"directory": "api"
|
|
33
33
|
},
|
|
34
|
-
"funding": "https://github.com/LaWebcapsule/
|
|
34
|
+
"funding": "https://github.com/LaWebcapsule/d9?sponsor=1",
|
|
35
35
|
"license": "GPL-3.0-only",
|
|
36
36
|
"author": {
|
|
37
37
|
"name": "Monospace Inc",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"openapi3-ts": "3.2.0",
|
|
86
86
|
"ora": "6.3.0",
|
|
87
87
|
"pino": "8.11.0",
|
|
88
|
-
"rollup": "3.
|
|
88
|
+
"rollup": "3.30.0",
|
|
89
89
|
"uuid": "9.0.0",
|
|
90
90
|
"wellknown": "0.5.0",
|
|
91
91
|
"@authenio/samlify-node-xmllint": "2.0.0",
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
"node-machine-id": "1.1.12",
|
|
128
128
|
"nodemailer": "7.0.11",
|
|
129
129
|
"object-hash": "3.0.0",
|
|
130
|
-
"openid-client": "
|
|
130
|
+
"openid-client": "6.8.2",
|
|
131
131
|
"otplib": "12.0.1",
|
|
132
132
|
"pino-http": "8.3.3",
|
|
133
133
|
"pino-http-print": "3.1.0",
|
|
@@ -143,20 +143,20 @@
|
|
|
143
143
|
"tmp-promise": "3.0.3",
|
|
144
144
|
"tsx": "3.12.6",
|
|
145
145
|
"uuid-validate": "0.0.3",
|
|
146
|
-
"@wbce-d9/app": "10.0.
|
|
147
|
-
"@wbce-d9/exceptions": "10.0.0",
|
|
148
|
-
"@wbce-d9/extensions-sdk": "10.0.2",
|
|
146
|
+
"@wbce-d9/app": "10.0.2",
|
|
149
147
|
"@wbce-d9/constants": "10.0.0",
|
|
148
|
+
"@wbce-d9/exceptions": "10.0.0",
|
|
150
149
|
"@wbce-d9/schema": "10.0.0",
|
|
151
150
|
"@wbce-d9/specs": "9.26.5",
|
|
152
151
|
"@wbce-d9/storage": "10.0.0",
|
|
153
152
|
"@wbce-d9/storage-driver-azure": "10.0.1",
|
|
153
|
+
"@wbce-d9/storage-driver-cloudinary": "10.0.1",
|
|
154
|
+
"@wbce-d9/extensions-sdk": "10.0.2",
|
|
154
155
|
"@wbce-d9/storage-driver-gcs": "10.0.1",
|
|
155
|
-
"@wbce-d9/storage-driver-s3": "10.0.1",
|
|
156
156
|
"@wbce-d9/storage-driver-local": "10.0.1",
|
|
157
|
-
"@wbce-d9/
|
|
157
|
+
"@wbce-d9/storage-driver-s3": "10.0.1",
|
|
158
158
|
"@wbce-d9/update-check": "10.0.1",
|
|
159
|
-
"@wbce-d9/
|
|
159
|
+
"@wbce-d9/utils": "10.0.1"
|
|
160
160
|
},
|
|
161
161
|
"devDependencies": {
|
|
162
162
|
"@ngneat/falso": "6.4.0",
|
|
@@ -170,11 +170,11 @@
|
|
|
170
170
|
"@types/supertest": "2.0.12",
|
|
171
171
|
"@types/uuid": "9.0.1",
|
|
172
172
|
"@types/wellknown": "0.5.4",
|
|
173
|
-
"@vitest/coverage-v8": "4.1.
|
|
173
|
+
"@vitest/coverage-v8": "4.1.4",
|
|
174
174
|
"@wbce-d9/tsconfig": "2.0.0",
|
|
175
175
|
"supertest": "6.3.3",
|
|
176
176
|
"typescript": "5.0.4",
|
|
177
|
-
"vitest": "4.1.
|
|
177
|
+
"vitest": "4.1.4",
|
|
178
178
|
"@types/async": "3.2.19",
|
|
179
179
|
"@types/busboy": "1.5.0",
|
|
180
180
|
"@types/bytes": "3.1.1",
|