@wbce-d9/api 12.2.0 → 12.4.0
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 +8 -8
- package/dist/auth/drivers/oauth2.d.ts +5 -5
- package/dist/auth/drivers/oauth2.js +26 -28
- package/dist/auth/drivers/openid.d.ts +5 -5
- package/dist/auth/drivers/openid.js +42 -52
- package/dist/cli/utils/create-env/env-stub.liquid +2 -2
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +14 -0
- package/dist/database/seeds/13-settings.yaml +1 -1
- package/dist/database/system-data/fields/files.yaml +2 -0
- package/dist/database/system-data/fields/users.yaml +3 -0
- package/dist/env.js +5 -0
- package/dist/exceptions/forbidden.js +1 -1
- package/dist/middleware/authenticate.js +1 -1
- package/dist/middleware/cors.js +1 -1
- package/dist/services/authentication.js +2 -2
- package/dist/services/authorization.js +7 -1
- package/dist/services/mail/index.js +1 -1
- package/dist/services/payload.js +6 -0
- package/dist/services/shares.js +1 -1
- package/dist/services/tfa.js +1 -1
- package/dist/types/auth.d.ts +1 -1
- package/dist/utils/compress.d.ts +1 -0
- package/dist/utils/generate-hash.js +1 -1
- package/dist/utils/get-ip-from-req.js +1 -1
- package/dist/utils/jwt.js +3 -3
- package/dist/utils/parse-image-metadata.d.ts +1 -0
- package/package.json +57 -57
|
@@ -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();
|
|
@@ -42,7 +42,7 @@ export class BaseOAuthDriver extends LocalAuthDriver {
|
|
|
42
42
|
// Update user to update refresh_token and other properties that might have changed
|
|
43
43
|
await this.getUserService().updateOne(userId, {
|
|
44
44
|
...updatedUserPayload,
|
|
45
|
-
auth_data: userPayload.auth_data,
|
|
45
|
+
auth_data: userPayload.auth_data ?? null,
|
|
46
46
|
});
|
|
47
47
|
return userId;
|
|
48
48
|
}
|
|
@@ -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, REFRESH_COOKIE_OPTIONS } from '../../constants.js';
|
|
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,15 +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
|
-
res.cookie(`oauth2.${providerName}`, token,
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
});
|
|
134
|
-
return res.redirect(provider.generateAuthUrl(codeVerifier, prompt));
|
|
135
|
-
}, respond);
|
|
131
|
+
res.cookie(`oauth2.${providerName}`, token, OAUTH2_COOKIE_OPTIONS);
|
|
132
|
+
return res.redirect(await provider.generateAuthUrl(codeVerifier, prompt));
|
|
133
|
+
}), respond);
|
|
136
134
|
router.post('/callback', express.urlencoded({ extended: false }), (req, res) => {
|
|
137
135
|
res.redirect(303, `./callback?${new URLSearchParams(req.body)}`);
|
|
138
136
|
}, respond);
|
|
@@ -164,8 +162,8 @@ export function createOAuth2AuthRouter(providerName) {
|
|
|
164
162
|
});
|
|
165
163
|
let authResponse;
|
|
166
164
|
try {
|
|
167
|
-
res.clearCookie(`oauth2.${providerName}`);
|
|
168
165
|
authResponse = await authenticationService.login(providerName, {
|
|
166
|
+
callbackPath: req.originalUrl,
|
|
169
167
|
code: req.query['code'],
|
|
170
168
|
codeVerifier: verifier,
|
|
171
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, REFRESH_COOKIE_OPTIONS } from '../../constants.js';
|
|
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,19 +151,17 @@ 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');
|
|
170
163
|
const cookieName = `openid.${providerName}.${state || ''}`;
|
|
171
|
-
res.cookie(cookieName, token,
|
|
172
|
-
httpOnly: true,
|
|
173
|
-
sameSite: 'lax',
|
|
174
|
-
});
|
|
164
|
+
res.cookie(cookieName, token, OAUTH2_COOKIE_OPTIONS);
|
|
175
165
|
return res.redirect(authUrl);
|
|
176
166
|
}), respond);
|
|
177
167
|
router.post('/callback', express.urlencoded({ extended: false }), (req, res) => {
|
|
@@ -214,13 +204,13 @@ export function createOpenIDAuthRouter(providerName) {
|
|
|
214
204
|
});
|
|
215
205
|
let authResponse;
|
|
216
206
|
try {
|
|
217
|
-
res.clearCookie(cookieName);
|
|
218
207
|
authResponse = await authenticationService.login(providerName, {
|
|
219
208
|
code: req.query['code'],
|
|
220
209
|
codeVerifier: verifier,
|
|
221
210
|
state: req.query['state'],
|
|
222
211
|
iss: req.query['iss'],
|
|
223
212
|
redirect: validRedirectUrl,
|
|
213
|
+
callbackPath: req.originalUrl,
|
|
224
214
|
});
|
|
225
215
|
}
|
|
226
216
|
catch (error) {
|
|
@@ -307,8 +307,8 @@ EXTENSIONS_AUTO_RELOAD=false
|
|
|
307
307
|
####################################################################################################
|
|
308
308
|
### Email
|
|
309
309
|
|
|
310
|
-
# Email address from which emails are sent
|
|
311
|
-
EMAIL_FROM="no-reply@
|
|
310
|
+
# Email address from which emails are sent
|
|
311
|
+
EMAIL_FROM="no-reply@example.com"
|
|
312
312
|
|
|
313
313
|
# What to use to send emails. One of
|
|
314
314
|
# sendmail, smtp, mailgun, sendgrid, ses.
|
package/dist/constants.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export declare const REFRESH_COOKIE_CLEAR_OPTIONS: CookieOptions;
|
|
|
13
13
|
export declare const GET_SET_HEADER: (cookieValue: string) => string;
|
|
14
14
|
export declare const ACCESS_COOKIE_OPTIONS: CookieOptions;
|
|
15
15
|
export declare const ACCESS_COOKIE_CLEAR_OPTIONS: CookieOptions;
|
|
16
|
+
export declare const OAUTH2_COOKIE_OPTIONS: CookieOptions;
|
|
17
|
+
export declare const OAUTH2_COOKIE_CLEAR_OPTIONS: CookieOptions;
|
|
16
18
|
export declare const OAS_REQUIRED_SCHEMAS: string[];
|
|
17
19
|
/** Formats from which transformation is supported */
|
|
18
20
|
export declare const SUPPORTED_IMAGE_TRANSFORM_FORMATS: string[];
|
package/dist/constants.js
CHANGED
|
@@ -82,6 +82,20 @@ export const ACCESS_COOKIE_CLEAR_OPTIONS = (() => {
|
|
|
82
82
|
delete rest.expires;
|
|
83
83
|
return rest;
|
|
84
84
|
})();
|
|
85
|
+
export const OAUTH2_COOKIE_OPTIONS = {
|
|
86
|
+
httpOnly: true,
|
|
87
|
+
domain: env['OAUTH2_TOKEN_COOKIE_DOMAIN'] || env['ACCESS_TOKEN_COOKIE_DOMAIN'],
|
|
88
|
+
maxAge: getMilliseconds(env['OAUTH2_TOKEN_TTL']),
|
|
89
|
+
secure: (env['OAUTH2_TOKEN_COOKIE_SECURE'] || env['ACCESS_TOKEN_COOKIE_SECURE']) ?? false,
|
|
90
|
+
sameSite: env['OAUTH2_TOKEN_COOKIE_SAME_SITE'] || 'lax',
|
|
91
|
+
path: '/auth',
|
|
92
|
+
};
|
|
93
|
+
export const OAUTH2_COOKIE_CLEAR_OPTIONS = (() => {
|
|
94
|
+
const { ...rest } = ACCESS_COOKIE_OPTIONS;
|
|
95
|
+
delete rest.maxAge;
|
|
96
|
+
delete rest.expires;
|
|
97
|
+
return rest;
|
|
98
|
+
})();
|
|
85
99
|
export const OAS_REQUIRED_SCHEMAS = ['Diff', 'Schema', 'Query', 'x-metadata'];
|
|
86
100
|
/** Formats from which transformation is supported */
|
|
87
101
|
export const SUPPORTED_IMAGE_TRANSFORM_FORMATS = ['image/jpeg', 'image/png', 'image/webp', 'image/tiff', 'image/avif'];
|
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
|
}
|
|
@@ -51,7 +51,7 @@ export const handler = async (req, _res, next) => {
|
|
|
51
51
|
})
|
|
52
52
|
.from('directus_sessions AS s')
|
|
53
53
|
.leftJoin('directus_users AS u', 's.user', 'u.id')
|
|
54
|
-
.where('s.
|
|
54
|
+
.where('s.session_id', payload.session_id)
|
|
55
55
|
.andWhere('s.expires', '>=', new Date())
|
|
56
56
|
.first();
|
|
57
57
|
if (!user) {
|
package/dist/middleware/cors.js
CHANGED
|
@@ -3,7 +3,7 @@ import env from '../env.js';
|
|
|
3
3
|
let corsMiddleware = (_req, _res, next) => next();
|
|
4
4
|
if (env['CORS_ENABLED'] === true) {
|
|
5
5
|
corsMiddleware = cors({
|
|
6
|
-
origin: env['CORS_ORIGIN'] ||
|
|
6
|
+
origin: env['CORS_ORIGIN'] || false,
|
|
7
7
|
methods: env['CORS_METHODS'] || 'GET,POST,PATCH,DELETE',
|
|
8
8
|
allowedHeaders: env['CORS_ALLOWED_HEADERS'],
|
|
9
9
|
exposedHeaders: env['CORS_EXPOSED_HEADERS'],
|
|
@@ -135,7 +135,7 @@ export class AuthenticationService {
|
|
|
135
135
|
role: user.role,
|
|
136
136
|
app_access: user.app_access,
|
|
137
137
|
admin_access: user.admin_access,
|
|
138
|
-
|
|
138
|
+
session_id: sessionId,
|
|
139
139
|
};
|
|
140
140
|
const customClaims = await emitter.emitFilter('auth.jwt', tokenPayload, {
|
|
141
141
|
status: 'pending',
|
|
@@ -309,7 +309,7 @@ export class AuthenticationService {
|
|
|
309
309
|
role: record.role_id,
|
|
310
310
|
app_access: record.role_app_access,
|
|
311
311
|
admin_access: record.role_admin_access,
|
|
312
|
-
|
|
312
|
+
session_id: sessionId,
|
|
313
313
|
};
|
|
314
314
|
if (record.share_id) {
|
|
315
315
|
tokenPayload.share = record.share_id;
|
|
@@ -449,7 +449,13 @@ export class AuthorizationService {
|
|
|
449
449
|
const originalItems = await mainItemService.readMany(keys);
|
|
450
450
|
for (let i = 0; i < originalItems.length; i++) {
|
|
451
451
|
const originalItem = originalItems[i];
|
|
452
|
-
const
|
|
452
|
+
const alreadyExistingItems = originalItem[fieldKey].length;
|
|
453
|
+
const updatedItemsIds = field.update.map((f) => f.id);
|
|
454
|
+
const deletedItemsIds = field.delete.map((f) => f?.id || f);
|
|
455
|
+
const twoTimesCountedItems = originalItem[fieldKey].reduce((n, item) => {
|
|
456
|
+
return n + Number(updatedItemsIds.includes(item) || deletedItemsIds.includes(item));
|
|
457
|
+
}, 0);
|
|
458
|
+
const emptyItemsToAdd = alreadyExistingItems - twoTimesCountedItems;
|
|
453
459
|
const finalArrayToValidate = arrayToValidate.concat(Array(emptyItemsToAdd).fill({}));
|
|
454
460
|
// validate partial items until the last key
|
|
455
461
|
if (i == keys.length - 1) {
|
|
@@ -72,7 +72,7 @@ export class MailService {
|
|
|
72
72
|
.from('directus_settings')
|
|
73
73
|
.first();
|
|
74
74
|
return {
|
|
75
|
-
projectName: projectInfo?.project_name || '
|
|
75
|
+
projectName: projectInfo?.project_name || 'd9',
|
|
76
76
|
projectColor: projectInfo?.project_color || '#546e7a',
|
|
77
77
|
projectLogo: getProjectLogoURL(projectInfo?.project_logo),
|
|
78
78
|
projectUrl: projectInfo?.project_url || '',
|
package/dist/services/payload.js
CHANGED
|
@@ -105,6 +105,12 @@ export class PayloadService {
|
|
|
105
105
|
return new Date(helpers.date.writeTimestamp(new Date().toISOString()));
|
|
106
106
|
return value;
|
|
107
107
|
},
|
|
108
|
+
async 'system-only'({ value, accountability }) {
|
|
109
|
+
if (accountability && (value === null || value)) {
|
|
110
|
+
throw new ForbiddenException();
|
|
111
|
+
}
|
|
112
|
+
return value;
|
|
113
|
+
},
|
|
108
114
|
async 'cast-csv'({ action, value }) {
|
|
109
115
|
if (Array.isArray(value) === false && typeof value !== 'string')
|
|
110
116
|
return;
|
package/dist/services/shares.js
CHANGED
|
@@ -81,7 +81,7 @@ export class SharesService extends ItemsService {
|
|
|
81
81
|
item: record.share_item,
|
|
82
82
|
collection: record.share_collection,
|
|
83
83
|
},
|
|
84
|
-
|
|
84
|
+
session_id: sessionId,
|
|
85
85
|
};
|
|
86
86
|
const accessToken = jwt.sign(tokenPayload, env['SECRET'], {
|
|
87
87
|
expiresIn: env['ACCESS_TOKEN_TTL'],
|
package/dist/services/tfa.js
CHANGED
|
@@ -31,7 +31,7 @@ export class TFAService {
|
|
|
31
31
|
const project = await this.knex.select('project_name').from('directus_settings').limit(1).first();
|
|
32
32
|
return {
|
|
33
33
|
secret,
|
|
34
|
-
url: authenticator.keyuri(user.email, project?.project_name || '
|
|
34
|
+
url: authenticator.keyuri(user.email, project?.project_name || 'd9', secret),
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
async enableTFA(key, otp, secret) {
|
package/dist/types/auth.d.ts
CHANGED
package/dist/utils/compress.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
3
|
export declare function compress(raw: Record<string, any> | Record<string, any>[]): Promise<Buffer>;
|
|
3
4
|
export declare function decompress(compressed: Buffer): Promise<any>;
|
|
@@ -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']));
|
|
@@ -13,5 +13,5 @@ export function getIPFromReq(req) {
|
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
// IP addresses starting with ::ffff: are IPv4 addresses in IPv6 format. We can strip the prefix to get back to IPv4
|
|
16
|
-
return ip
|
|
16
|
+
return ip?.startsWith('::ffff:') ? ip.substring(7) : ip ?? '';
|
|
17
17
|
}
|
package/dist/utils/jwt.js
CHANGED
|
@@ -21,9 +21,9 @@ export function verifyJWT(token, secret) {
|
|
|
21
21
|
return payload;
|
|
22
22
|
}
|
|
23
23
|
export function verifyAccessJWT(token, secret) {
|
|
24
|
-
const { id, role, app_access, admin_access, share, share_scope,
|
|
25
|
-
if (role === undefined || app_access === undefined || admin_access === undefined ||
|
|
24
|
+
const { id, role, app_access, admin_access, share, share_scope, session_id } = verifyJWT(token, secret);
|
|
25
|
+
if (role === undefined || app_access === undefined || admin_access === undefined || session_id === undefined) {
|
|
26
26
|
throw new InvalidTokenException('Invalid token payload.');
|
|
27
27
|
}
|
|
28
|
-
return { id, role, app_access, admin_access, share, share_scope,
|
|
28
|
+
return { id, role, app_access, admin_access, share, share_scope, session_id };
|
|
29
29
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wbce-d9/api",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.4.0",
|
|
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://
|
|
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",
|
|
@@ -63,75 +63,77 @@
|
|
|
63
63
|
"!**/__*__/"
|
|
64
64
|
],
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@authenio/samlify-node-xmllint": "2.0.0",
|
|
67
|
-
"@aws-sdk/client-sesv2": "3.916.0",
|
|
68
|
-
"@godaddy/terminus": "4.12.0",
|
|
69
|
-
"@rollup/plugin-alias": "5.0.0",
|
|
70
66
|
"@rollup/plugin-node-resolve": "15.0.2",
|
|
71
67
|
"@rollup/plugin-virtual": "3.0.1",
|
|
72
68
|
"@wbce-d9/format-title": "10.0.0",
|
|
73
|
-
"argon2": "0.30.3",
|
|
74
|
-
"async": "3.2.4",
|
|
75
69
|
"axios": "^1.12.0",
|
|
70
|
+
"chalk": "5.2.0",
|
|
71
|
+
"commander": "10.0.1",
|
|
72
|
+
"date-fns": "2.29.3",
|
|
73
|
+
"execa": "7.1.1",
|
|
74
|
+
"express": "^4.21.2",
|
|
75
|
+
"fs-extra": "11.1.1",
|
|
76
|
+
"inquirer": "9.1.5",
|
|
77
|
+
"joi": "17.9.1",
|
|
78
|
+
"js-yaml": "4.1.1",
|
|
79
|
+
"json2csv": "5.0.7",
|
|
80
|
+
"knex": "3.1.0",
|
|
81
|
+
"lodash-es": "4.17.23",
|
|
82
|
+
"marked": "4.3.0",
|
|
83
|
+
"micromustache": "8.0.3",
|
|
84
|
+
"nanoid": "5.0.9",
|
|
85
|
+
"openapi3-ts": "3.2.0",
|
|
86
|
+
"ora": "6.3.0",
|
|
87
|
+
"pino": "8.11.0",
|
|
88
|
+
"rollup": "3.30.0",
|
|
89
|
+
"uuid": "9.0.0",
|
|
90
|
+
"wellknown": "0.5.0",
|
|
91
|
+
"@authenio/samlify-node-xmllint": "2.0.0",
|
|
92
|
+
"@aws-sdk/client-sesv2": "3.1004.0",
|
|
93
|
+
"@godaddy/terminus": "4.12.0",
|
|
94
|
+
"@rollup/plugin-alias": "5.0.0",
|
|
95
|
+
"argon2": "0.44.0",
|
|
96
|
+
"async": "3.2.4",
|
|
76
97
|
"busboy": "1.6.0",
|
|
77
98
|
"bytes": "3.1.2",
|
|
78
99
|
"camelcase": "7.0.1",
|
|
79
|
-
"chalk": "5.2.0",
|
|
80
100
|
"chokidar": "3.5.3",
|
|
81
|
-
"commander": "10.0.1",
|
|
82
101
|
"content-disposition": "0.5.4",
|
|
83
102
|
"cookie-parser": "1.4.7",
|
|
84
103
|
"cors": "2.8.5",
|
|
85
104
|
"csv-parser": "3.0.0",
|
|
86
|
-
"date-fns": "2.29.3",
|
|
87
105
|
"deep-diff": "1.0.2",
|
|
88
106
|
"destroy": "1.2.0",
|
|
89
107
|
"dotenv": "16.0.3",
|
|
90
108
|
"encodeurl": "1.0.2",
|
|
91
109
|
"eventemitter2": "6.4.9",
|
|
92
|
-
"execa": "7.1.1",
|
|
93
110
|
"exif-reader": "1.2.0",
|
|
94
|
-
"express": "^4.21.2",
|
|
95
111
|
"fast-redact": "^3.5.0",
|
|
96
112
|
"flat": "5.0.2",
|
|
97
|
-
"fs-extra": "11.1.1",
|
|
98
113
|
"graphql": "16.8.1",
|
|
99
114
|
"graphql-compose": "9.0.10",
|
|
100
115
|
"helmet": "6.1.5",
|
|
101
116
|
"icc": "3.0.0",
|
|
102
|
-
"inquirer": "9.1.5",
|
|
103
117
|
"ioredis": "5.3.2",
|
|
104
118
|
"isolated-vm": "^6.0.1",
|
|
105
|
-
"joi": "17.9.1",
|
|
106
|
-
"js-yaml": "4.1.1",
|
|
107
119
|
"js2xmlparser": "5.0.0",
|
|
108
|
-
"json2csv": "5.0.7",
|
|
109
120
|
"jsonwebtoken": "9.0.0",
|
|
110
121
|
"keyv": "4.5.2",
|
|
111
|
-
"knex": "3.1.0",
|
|
112
122
|
"ldapjs": "2.3.3",
|
|
113
123
|
"liquidjs": "10.7.0",
|
|
114
|
-
"lodash-es": "4.17.23",
|
|
115
|
-
"marked": "4.3.0",
|
|
116
|
-
"micromustache": "8.0.3",
|
|
117
124
|
"mime-types": "2.1.35",
|
|
118
125
|
"ms": "2.1.3",
|
|
119
|
-
"nanoid": "5.0.9",
|
|
120
126
|
"node-cron": "3.0.2",
|
|
121
127
|
"node-machine-id": "1.1.12",
|
|
122
128
|
"nodemailer": "7.0.11",
|
|
123
129
|
"object-hash": "3.0.0",
|
|
124
|
-
"
|
|
125
|
-
"openid-client": "5.4.0",
|
|
126
|
-
"ora": "6.3.0",
|
|
130
|
+
"openid-client": "6.8.2",
|
|
127
131
|
"otplib": "12.0.1",
|
|
128
|
-
"pino": "8.11.0",
|
|
129
132
|
"pino-http": "8.3.3",
|
|
130
133
|
"pino-http-print": "3.1.0",
|
|
131
134
|
"pino-pretty": "10.0.0",
|
|
132
|
-
"qs": "6.14.
|
|
135
|
+
"qs": "6.14.2",
|
|
133
136
|
"rate-limiter-flexible": "2.4.1",
|
|
134
|
-
"rollup": "3.29.5",
|
|
135
137
|
"samlify": "2.10.0",
|
|
136
138
|
"sanitize-html": "2.12.1",
|
|
137
139
|
"sharp": "0.32.6",
|
|
@@ -140,26 +142,39 @@
|
|
|
140
142
|
"strip-bom-stream": "5.0.0",
|
|
141
143
|
"tmp-promise": "3.0.3",
|
|
142
144
|
"tsx": "3.12.6",
|
|
143
|
-
"uuid": "9.0.0",
|
|
144
145
|
"uuid-validate": "0.0.3",
|
|
145
|
-
"wellknown": "0.5.0",
|
|
146
146
|
"@wbce-d9/constants": "10.0.0",
|
|
147
|
-
"@wbce-d9/app": "9.35.1",
|
|
148
147
|
"@wbce-d9/exceptions": "10.0.0",
|
|
149
148
|
"@wbce-d9/extensions-sdk": "10.0.2",
|
|
149
|
+
"@wbce-d9/schema": "10.0.0",
|
|
150
150
|
"@wbce-d9/specs": "9.26.5",
|
|
151
151
|
"@wbce-d9/storage": "10.0.0",
|
|
152
|
+
"@wbce-d9/app": "10.0.0",
|
|
152
153
|
"@wbce-d9/storage-driver-azure": "10.0.1",
|
|
153
|
-
"@wbce-d9/
|
|
154
|
+
"@wbce-d9/storage-driver-gcs": "10.0.1",
|
|
154
155
|
"@wbce-d9/storage-driver-cloudinary": "10.0.1",
|
|
155
156
|
"@wbce-d9/storage-driver-local": "10.0.1",
|
|
156
157
|
"@wbce-d9/storage-driver-s3": "10.0.1",
|
|
157
|
-
"@wbce-d9/
|
|
158
|
-
"@wbce-d9/update-check": "10.0.1"
|
|
159
|
-
"@wbce-d9/utils": "10.0.1"
|
|
158
|
+
"@wbce-d9/utils": "10.0.1",
|
|
159
|
+
"@wbce-d9/update-check": "10.0.1"
|
|
160
160
|
},
|
|
161
161
|
"devDependencies": {
|
|
162
162
|
"@ngneat/falso": "6.4.0",
|
|
163
|
+
"@types/fs-extra": "11.0.1",
|
|
164
|
+
"@types/inquirer": "9.0.3",
|
|
165
|
+
"@types/js-yaml": "4.0.5",
|
|
166
|
+
"@types/json2csv": "5.0.3",
|
|
167
|
+
"@types/lodash-es": "4.17.7",
|
|
168
|
+
"@types/marked": "4.0.8",
|
|
169
|
+
"@types/node": "22.15.29",
|
|
170
|
+
"@types/supertest": "2.0.12",
|
|
171
|
+
"@types/uuid": "9.0.1",
|
|
172
|
+
"@types/wellknown": "0.5.4",
|
|
173
|
+
"@vitest/coverage-v8": "4.1.0",
|
|
174
|
+
"@wbce-d9/tsconfig": "2.0.0",
|
|
175
|
+
"supertest": "6.3.3",
|
|
176
|
+
"typescript": "5.0.4",
|
|
177
|
+
"vitest": "4.1.0",
|
|
163
178
|
"@types/async": "3.2.19",
|
|
164
179
|
"@types/busboy": "1.5.0",
|
|
165
180
|
"@types/bytes": "3.1.1",
|
|
@@ -170,40 +185,25 @@
|
|
|
170
185
|
"@types/destroy": "1.0.0",
|
|
171
186
|
"@types/encodeurl": "1.0.0",
|
|
172
187
|
"@types/exif-reader": "1.0.0",
|
|
173
|
-
"@types/express": "4.17.
|
|
174
|
-
"@types/express-serve-static-core": "4.
|
|
188
|
+
"@types/express": "4.17.25",
|
|
189
|
+
"@types/express-serve-static-core": "4.19.8",
|
|
175
190
|
"@types/fast-redact": "3.0.2",
|
|
176
191
|
"@types/flat": "5.0.2",
|
|
177
|
-
"@types/fs-extra": "11.0.1",
|
|
178
|
-
"@types/inquirer": "9.0.3",
|
|
179
|
-
"@types/js-yaml": "4.0.5",
|
|
180
|
-
"@types/json2csv": "5.0.3",
|
|
181
192
|
"@types/jsonwebtoken": "9.0.1",
|
|
182
193
|
"@types/keyv": "4.2.0",
|
|
183
194
|
"@types/ldapjs": "2.2.5",
|
|
184
|
-
"@types/lodash-es": "4.17.7",
|
|
185
|
-
"@types/marked": "4.0.8",
|
|
186
195
|
"@types/mime-types": "2.1.1",
|
|
187
196
|
"@types/ms": "0.7.31",
|
|
188
|
-
"@types/node": "18.15.13",
|
|
189
197
|
"@types/node-cron": "3.0.7",
|
|
190
198
|
"@types/nodemailer": "6.4.7",
|
|
191
199
|
"@types/object-hash": "3.0.2",
|
|
192
|
-
"@types/qs": "6.
|
|
200
|
+
"@types/qs": "6.14.0",
|
|
193
201
|
"@types/sanitize-html": "2.9.0",
|
|
194
202
|
"@types/stream-json": "1.7.3",
|
|
195
|
-
"@types/supertest": "2.0.12",
|
|
196
|
-
"@types/uuid": "9.0.1",
|
|
197
203
|
"@types/uuid-validate": "0.0.1",
|
|
198
|
-
"@types/wellknown": "0.5.4",
|
|
199
|
-
"@vitest/coverage-c8": "0.30.1",
|
|
200
|
-
"@wbce-d9/tsconfig": "2.0.0",
|
|
201
204
|
"copyfiles": "2.4.1",
|
|
202
205
|
"form-data": "4.0.4",
|
|
203
206
|
"knex-mock-client": "3.0.2",
|
|
204
|
-
"supertest": "6.3.3",
|
|
205
|
-
"typescript": "5.0.4",
|
|
206
|
-
"vitest": "0.30.1",
|
|
207
207
|
"@wbce-d9/types": "10.1.0"
|
|
208
208
|
},
|
|
209
209
|
"optionalDependencies": {
|