@wbce-d9/api 9.28.0 → 10.0.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.
@@ -0,0 +1,28 @@
1
+ import type { Client, TokenSet } from 'openid-client';
2
+ import { UsersService } from '../../services/users.js';
3
+ import type { User } from '../../types/index.js';
4
+ import { LocalAuthDriver } from './local.js';
5
+ export interface UserPayload {
6
+ provider: string;
7
+ first_name: any;
8
+ last_name: any;
9
+ email: any;
10
+ external_identifier: string;
11
+ role: string;
12
+ auth_data: any;
13
+ }
14
+ export declare abstract class BaseOAuthDriver extends LocalAuthDriver {
15
+ abstract getClient(): Promise<Client>;
16
+ abstract getredirectUrl(): string;
17
+ abstract getUserService(): UsersService;
18
+ abstract getConfig(): Record<string, any>;
19
+ abstract getClientName(): string;
20
+ abstract getTokenSetAndUserInfo(payload: Record<string, any>): Promise<[TokenSet, Record<string, unknown>, UserPayload]>;
21
+ generateCodeVerifier(): string;
22
+ fetchUserId(identifier: string): Promise<string | undefined>;
23
+ getUserID(payload: Record<string, any>): Promise<string>;
24
+ logout(user: User): Promise<void>;
25
+ login(user: User): Promise<void>;
26
+ refresh(user: User): Promise<void>;
27
+ handleError: (e: any) => any;
28
+ }
@@ -0,0 +1,162 @@
1
+ import { parseJSON } from '@wbce-d9/utils';
2
+ import { errors, generators } from 'openid-client';
3
+ import getDatabase from '../../database/index.js';
4
+ import emitter from '../../emitter.js';
5
+ import { RecordNotUniqueException } from '../../exceptions/database/record-not-unique.js';
6
+ import { InvalidCredentialsException, InvalidProviderException, InvalidTokenException, ServiceUnavailableException, } from '../../exceptions/index.js';
7
+ import logger from '../../logger.js';
8
+ import { UsersService } from '../../services/users.js';
9
+ import { LocalAuthDriver } from './local.js';
10
+ export class BaseOAuthDriver extends LocalAuthDriver {
11
+ generateCodeVerifier() {
12
+ return generators.codeVerifier();
13
+ }
14
+ async fetchUserId(identifier) {
15
+ const user = await this.knex.select('id').from('directus_users').where('external_identifier', identifier).first();
16
+ return user?.id;
17
+ }
18
+ async getUserID(payload) {
19
+ if (!payload['code'] || !payload['codeVerifier'] || !payload['state']) {
20
+ logger.warn(`[${this.getClientName()}] No code, codeVerifier or state in payload`);
21
+ throw new InvalidCredentialsException();
22
+ }
23
+ const [tokenSet, userInfo, userPayload] = await this.getTokenSetAndUserInfo(payload);
24
+ const identifier = userPayload.external_identifier;
25
+ const userId = await this.fetchUserId(identifier);
26
+ if (userId) {
27
+ let updateReq = {};
28
+ if (this.getConfig()['updateUserOnLogin'] ?? false) {
29
+ updateReq = {
30
+ first_name: userPayload.first_name,
31
+ last_name: userPayload.last_name,
32
+ email: userPayload.email,
33
+ };
34
+ }
35
+ // Run hook so the end user has the chance to augment the
36
+ // user that is about to be updated
37
+ const updatedUserPayload = await emitter.emitFilter(`auth.update`, updateReq, {
38
+ identifier,
39
+ provider: this.getConfig()['provider'],
40
+ providerPayload: { accessToken: tokenSet.access_token, userInfo },
41
+ }, { database: getDatabase(), schema: this.schema, accountability: null });
42
+ // Update user to update refresh_token and other properties that might have changed
43
+ await this.getUserService().updateOne(userId, {
44
+ ...updatedUserPayload,
45
+ auth_data: userPayload.auth_data,
46
+ });
47
+ return userId;
48
+ }
49
+ // --- NEW USER ----
50
+ const { provider, allowPublicRegistration, requireVerifiedEmail } = this.getConfig();
51
+ const isEmailVerified = !requireVerifiedEmail || userInfo['email_verified'];
52
+ // Is public registration allowed?
53
+ if (!allowPublicRegistration || !isEmailVerified) {
54
+ logger.warn(`[${this.getClientName()}] User doesn't exist, and public registration not allowed for provider "${provider}"`);
55
+ throw new InvalidCredentialsException();
56
+ }
57
+ // Run hook so the end user has the chance to augment the
58
+ // user that is about to be created
59
+ const updatedUserPayload = await emitter.emitFilter(`auth.create`, userPayload, {
60
+ identifier,
61
+ provider: this.getConfig()['provider'],
62
+ providerPayload: { accessToken: tokenSet.access_token, userInfo },
63
+ }, { database: getDatabase(), schema: this.schema, accountability: null });
64
+ try {
65
+ await this.getUserService().createOne(updatedUserPayload);
66
+ }
67
+ catch (e) {
68
+ if (e instanceof RecordNotUniqueException) {
69
+ logger.warn(e, `[${this.getClientName()}] Failed to register user. User not unique`);
70
+ throw new InvalidProviderException();
71
+ }
72
+ throw e;
73
+ }
74
+ return (await this.fetchUserId(identifier));
75
+ }
76
+ async logout(user) {
77
+ let authData = user.auth_data;
78
+ if (typeof authData === 'string') {
79
+ try {
80
+ authData = parseJSON(authData);
81
+ }
82
+ catch {
83
+ logger.warn(`[${this.getClientName()}] Session data isn't valid JSON: ${authData}`);
84
+ }
85
+ }
86
+ if (authData?.['refreshToken']) {
87
+ try {
88
+ const client = await this.getClient();
89
+ const tokenSet = await client.refresh(authData['refreshToken']);
90
+ if (tokenSet.refresh_token) {
91
+ await client.revoke(tokenSet.refresh_token, 'refresh_token');
92
+ }
93
+ await this.getUserService().updateOne(user.id, {
94
+ auth_data: null,
95
+ });
96
+ logger.info(`[${this.getClientName()}] Session closed for user.`);
97
+ }
98
+ catch (e) {
99
+ throw this.handleError(e);
100
+ }
101
+ }
102
+ else {
103
+ logger.warn(`[${this.getClientName()}] Session closed for user without auth_data`);
104
+ }
105
+ }
106
+ async login(user) {
107
+ return this.refresh(user);
108
+ }
109
+ async refresh(user) {
110
+ let authData = user.auth_data;
111
+ if (typeof authData === 'string') {
112
+ try {
113
+ authData = parseJSON(authData);
114
+ }
115
+ catch {
116
+ logger.warn(`[${this.getClientName()}] Session data isn't valid JSON: ${authData}`);
117
+ }
118
+ }
119
+ if (authData?.['refreshToken']) {
120
+ try {
121
+ const client = await this.getClient();
122
+ const tokenSet = await client.refresh(authData['refreshToken']);
123
+ // Update user refreshToken if provided
124
+ if (tokenSet.refresh_token) {
125
+ await this.getUserService().updateOne(user.id, {
126
+ auth_data: JSON.stringify({ refreshToken: tokenSet.refresh_token }),
127
+ });
128
+ }
129
+ }
130
+ catch (e) {
131
+ throw this.handleError(e);
132
+ }
133
+ }
134
+ else {
135
+ // If no auth connexion, throw user out
136
+ logger.error(`No [${this.getClientName()}] Session found`);
137
+ throw new InvalidCredentialsException();
138
+ }
139
+ }
140
+ handleError = (e) => {
141
+ if (e instanceof errors.OPError) {
142
+ if (e.error === 'invalid_grant') {
143
+ // Invalid token
144
+ logger.trace(e, `[${this.getClientName()}] Invalid grant`);
145
+ return new InvalidTokenException();
146
+ }
147
+ // Server response error
148
+ logger.trace(e, `[${this.getClientName()}] Unknown OP error`);
149
+ return new ServiceUnavailableException('Service returned unexpected response', {
150
+ service: 'openid',
151
+ message: e.error_description,
152
+ });
153
+ }
154
+ else if (e instanceof errors.RPError) {
155
+ // Internal client error
156
+ logger.trace(e, `[${this.getClientName()}] Unknown RP error`);
157
+ return new InvalidCredentialsException();
158
+ }
159
+ logger.trace(e, `[${this.getClientName()}] Unknown error`);
160
+ return e;
161
+ };
162
+ }
@@ -1,19 +1,20 @@
1
1
  import { Router } from 'express';
2
- import type { Client } from 'openid-client';
2
+ import type { BaseClient, Client, TokenSet } from 'openid-client';
3
3
  import { UsersService } from '../../services/users.js';
4
- import type { AuthDriverOptions, User } from '../../types/index.js';
5
- import { LocalAuthDriver } from './local.js';
6
- export declare class OAuth2AuthDriver extends LocalAuthDriver {
4
+ import type { AuthDriverOptions } from '../../types/index.js';
5
+ import { BaseOAuthDriver, type UserPayload } from './baseoauth.js';
6
+ export declare class OAuth2AuthDriver extends BaseOAuthDriver {
7
7
  client: Client;
8
8
  redirectUrl: string;
9
9
  usersService: UsersService;
10
10
  config: Record<string, any>;
11
+ getClient(): Promise<BaseClient>;
12
+ getredirectUrl(): string;
13
+ getUserService(): UsersService;
14
+ getConfig(): Record<string, any>;
15
+ getClientName(): string;
11
16
  constructor(options: AuthDriverOptions, config: Record<string, any>);
12
- generateCodeVerifier(): string;
13
17
  generateAuthUrl(codeVerifier: string, prompt?: boolean): string;
14
- private fetchUserId;
15
- getUserID(payload: Record<string, any>): Promise<string>;
16
- login(user: User): Promise<void>;
17
- refresh(user: User): Promise<void>;
18
+ getTokenSetAndUserInfo(payload: Record<string, any>): Promise<[TokenSet, Record<string, unknown>, UserPayload]>;
18
19
  }
19
20
  export declare function createOAuth2AuthRouter(providerName: string): Router;
@@ -1,15 +1,11 @@
1
1
  import { BaseException } from '@wbce-d9/exceptions';
2
- import { parseJSON } from '@wbce-d9/utils';
3
2
  import express, { Router } from 'express';
4
3
  import flatten from 'flat';
5
4
  import jwt from 'jsonwebtoken';
6
- import { errors, generators, Issuer } from 'openid-client';
5
+ import { generators, Issuer } from 'openid-client';
7
6
  import { getAuthProvider } from '../../auth.js';
8
- import getDatabase from '../../database/index.js';
9
- import emitter from '../../emitter.js';
10
7
  import env from '../../env.js';
11
- import { RecordNotUniqueException } from '../../exceptions/database/record-not-unique.js';
12
- import { InvalidConfigException, InvalidCredentialsException, InvalidProviderException, InvalidTokenException, ServiceUnavailableException, } from '../../exceptions/index.js';
8
+ import { InvalidConfigException, InvalidCredentialsException, InvalidTokenException } from '../../exceptions/index.js';
13
9
  import logger from '../../logger.js';
14
10
  import { respond } from '../../middleware/respond.js';
15
11
  import { AuthenticationService } from '../../services/authentication.js';
@@ -19,12 +15,27 @@ import { getConfigFromEnv } from '../../utils/get-config-from-env.js';
19
15
  import { getIPFromReq } from '../../utils/get-ip-from-req.js';
20
16
  import { getMilliseconds } from '../../utils/get-milliseconds.js';
21
17
  import { Url } from '../../utils/url.js';
22
- import { LocalAuthDriver } from './local.js';
23
- export class OAuth2AuthDriver extends LocalAuthDriver {
18
+ import { BaseOAuthDriver } from './baseoauth.js';
19
+ export class OAuth2AuthDriver extends BaseOAuthDriver {
24
20
  client;
25
21
  redirectUrl;
26
22
  usersService;
27
23
  config;
24
+ getClient() {
25
+ return Promise.resolve(this.client);
26
+ }
27
+ getredirectUrl() {
28
+ return this.redirectUrl;
29
+ }
30
+ getUserService() {
31
+ return this.usersService;
32
+ }
33
+ getConfig() {
34
+ return this.config;
35
+ }
36
+ getClientName() {
37
+ return 'OAuth2';
38
+ }
28
39
  constructor(options, config) {
29
40
  super(options, config);
30
41
  const { authorizeUrl, accessUrl, profileUrl, clientId, clientSecret, ...additionalConfig } = config;
@@ -50,9 +61,6 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
50
61
  ...clientOptionsOverrides,
51
62
  });
52
63
  }
53
- generateCodeVerifier() {
54
- return generators.codeVerifier();
55
- }
56
64
  generateAuthUrl(codeVerifier, prompt = false) {
57
65
  try {
58
66
  const codeChallenge = generators.codeChallenge(codeVerifier);
@@ -69,22 +77,10 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
69
77
  });
70
78
  }
71
79
  catch (e) {
72
- throw handleError(e);
80
+ throw this.handleError(e);
73
81
  }
74
82
  }
75
- async fetchUserId(identifier) {
76
- const user = await this.knex
77
- .select('id')
78
- .from('directus_users')
79
- .whereRaw('LOWER(??) = ?', ['external_identifier', identifier.toLowerCase()])
80
- .first();
81
- return user?.id;
82
- }
83
- async getUserID(payload) {
84
- if (!payload['code'] || !payload['codeVerifier'] || !payload['state']) {
85
- logger.warn('[OAuth2] No code, codeVerifier or state in payload');
86
- throw new InvalidCredentialsException();
87
- }
83
+ async getTokenSetAndUserInfo(payload) {
88
84
  let tokenSet;
89
85
  let userInfo;
90
86
  try {
@@ -92,11 +88,11 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
92
88
  userInfo = await this.client.userinfo(tokenSet.access_token);
93
89
  }
94
90
  catch (e) {
95
- throw handleError(e);
91
+ throw this.handleError(e);
96
92
  }
97
93
  // Flatten response to support dot indexes
98
94
  userInfo = flatten(userInfo);
99
- const { provider, emailKey, identifierKey, allowPublicRegistration } = this.config;
95
+ const { provider, emailKey, identifierKey } = this.config;
100
96
  const email = userInfo[emailKey ?? 'email'] ? String(userInfo[emailKey ?? 'email']) : undefined;
101
97
  // Fallback to email if explicit identifier not found
102
98
  const identifier = userInfo[identifierKey] ? String(userInfo[identifierKey]) : email;
@@ -113,94 +109,9 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
113
109
  role: this.config['defaultRoleId'],
114
110
  auth_data: tokenSet.refresh_token && JSON.stringify({ refreshToken: tokenSet.refresh_token }),
115
111
  };
116
- const userId = await this.fetchUserId(identifier);
117
- if (userId) {
118
- // Run hook so the end user has the chance to augment the
119
- // user that is about to be updated
120
- const updatedUserPayload = await emitter.emitFilter(`auth.update`, {}, {
121
- identifier,
122
- provider: this.config['provider'],
123
- providerPayload: { accessToken: tokenSet.access_token, userInfo },
124
- }, { database: getDatabase(), schema: this.schema, accountability: null });
125
- // Update user to update refresh_token and other properties that might have changed
126
- await this.usersService.updateOne(userId, updatedUserPayload);
127
- return userId;
128
- }
129
- // Is public registration allowed?
130
- if (!allowPublicRegistration) {
131
- logger.warn(`[OAuth2] User doesn't exist, and public registration not allowed for provider "${provider}"`);
132
- throw new InvalidCredentialsException();
133
- }
134
- // Run hook so the end user has the chance to augment the
135
- // user that is about to be created
136
- const updatedUserPayload = await emitter.emitFilter(`auth.create`, userPayload, {
137
- identifier,
138
- provider: this.config['provider'],
139
- providerPayload: { accessToken: tokenSet.access_token, userInfo },
140
- }, { database: getDatabase(), schema: this.schema, accountability: null });
141
- try {
142
- await this.usersService.createOne(updatedUserPayload);
143
- }
144
- catch (e) {
145
- if (e instanceof RecordNotUniqueException) {
146
- logger.warn(e, '[OAuth2] Failed to register user. User not unique');
147
- throw new InvalidProviderException();
148
- }
149
- throw e;
150
- }
151
- return (await this.fetchUserId(identifier));
152
- }
153
- async login(user) {
154
- return this.refresh(user);
155
- }
156
- async refresh(user) {
157
- let authData = user.auth_data;
158
- if (typeof authData === 'string') {
159
- try {
160
- authData = parseJSON(authData);
161
- }
162
- catch {
163
- logger.warn(`[OAuth2] Session data isn't valid JSON: ${authData}`);
164
- }
165
- }
166
- if (authData?.['refreshToken']) {
167
- try {
168
- const tokenSet = await this.client.refresh(authData['refreshToken']);
169
- // Update user refreshToken if provided
170
- if (tokenSet.refresh_token) {
171
- await this.usersService.updateOne(user.id, {
172
- auth_data: JSON.stringify({ refreshToken: tokenSet.refresh_token }),
173
- });
174
- }
175
- }
176
- catch (e) {
177
- throw handleError(e);
178
- }
179
- }
112
+ return [tokenSet, userInfo, userPayload];
180
113
  }
181
114
  }
182
- const handleError = (e) => {
183
- if (e instanceof errors.OPError) {
184
- if (e.error === 'invalid_grant') {
185
- // Invalid token
186
- logger.trace(e, `[OAuth2] Invalid grant`);
187
- return new InvalidTokenException();
188
- }
189
- // Server response error
190
- logger.trace(e, `[OAuth2] Unknown OP error`);
191
- return new ServiceUnavailableException('Service returned unexpected response', {
192
- service: 'oauth2',
193
- message: e.error_description,
194
- });
195
- }
196
- else if (e instanceof errors.RPError) {
197
- // Internal client error
198
- logger.trace(e, `[OAuth2] Unknown RP error`);
199
- return new InvalidCredentialsException();
200
- }
201
- logger.trace(e, `[OAuth2] Unknown error`);
202
- return e;
203
- };
204
115
  export function createOAuth2AuthRouter(providerName) {
205
116
  const router = Router();
206
117
  router.get('/', (req, res) => {
@@ -1,19 +1,20 @@
1
1
  import { Router } from 'express';
2
- import type { Client } from 'openid-client';
2
+ import type { AuthorizationParameters, BaseClient, Client, TokenSet } from 'openid-client';
3
3
  import { UsersService } from '../../services/users.js';
4
- import type { AuthDriverOptions, User } from '../../types/index.js';
5
- import { LocalAuthDriver } from './local.js';
6
- export declare class OpenIDAuthDriver extends LocalAuthDriver {
4
+ import type { AuthDriverOptions } from '../../types/index.js';
5
+ import { BaseOAuthDriver, type UserPayload } from './baseoauth.js';
6
+ export declare class OpenIDAuthDriver extends BaseOAuthDriver {
7
7
  client: Promise<Client>;
8
8
  redirectUrl: string;
9
9
  usersService: UsersService;
10
10
  config: Record<string, any>;
11
+ getClient(): Promise<BaseClient>;
12
+ getredirectUrl(): string;
13
+ getUserService(): UsersService;
14
+ getConfig(): Record<string, any>;
15
+ getClientName(): string;
11
16
  constructor(options: AuthDriverOptions, config: Record<string, any>);
12
- generateCodeVerifier(): string;
13
- generateAuthUrl(codeVerifier: string, prompt?: boolean): Promise<string>;
14
- private fetchUserId;
15
- getUserID(payload: Record<string, any>): Promise<string>;
16
- login(user: User): Promise<void>;
17
- refresh(user: User): Promise<void>;
17
+ generateAuthUrl(codeVerifier: string, prompt?: boolean, additionalParams?: AuthorizationParameters | undefined): Promise<string>;
18
+ getTokenSetAndUserInfo(payload: Record<string, any>): Promise<[TokenSet, Record<string, unknown>, UserPayload]>;
18
19
  }
19
20
  export declare function createOpenIDAuthRouter(providerName: string): Router;
@@ -1,15 +1,11 @@
1
1
  import { BaseException } from '@wbce-d9/exceptions';
2
- import { parseJSON } from '@wbce-d9/utils';
3
2
  import express, { Router } from 'express';
4
3
  import flatten from 'flat';
5
4
  import jwt from 'jsonwebtoken';
6
- import { errors, generators, Issuer } from 'openid-client';
5
+ import { generators, Issuer } from 'openid-client';
7
6
  import { getAuthProvider } from '../../auth.js';
8
- import getDatabase from '../../database/index.js';
9
- import emitter from '../../emitter.js';
10
7
  import env from '../../env.js';
11
- import { RecordNotUniqueException } from '../../exceptions/database/record-not-unique.js';
12
- import { InvalidConfigException, InvalidCredentialsException, InvalidProviderException, InvalidTokenException, ServiceUnavailableException, } from '../../exceptions/index.js';
8
+ import { InvalidConfigException, InvalidCredentialsException, InvalidTokenException } from '../../exceptions/index.js';
13
9
  import logger from '../../logger.js';
14
10
  import { respond } from '../../middleware/respond.js';
15
11
  import { AuthenticationService } from '../../services/authentication.js';
@@ -19,12 +15,27 @@ import { getConfigFromEnv } from '../../utils/get-config-from-env.js';
19
15
  import { getIPFromReq } from '../../utils/get-ip-from-req.js';
20
16
  import { getMilliseconds } from '../../utils/get-milliseconds.js';
21
17
  import { Url } from '../../utils/url.js';
22
- import { LocalAuthDriver } from './local.js';
23
- export class OpenIDAuthDriver extends LocalAuthDriver {
18
+ import { BaseOAuthDriver } from './baseoauth.js';
19
+ export class OpenIDAuthDriver extends BaseOAuthDriver {
24
20
  client;
25
21
  redirectUrl;
26
22
  usersService;
27
23
  config;
24
+ getClient() {
25
+ return this.client;
26
+ }
27
+ getredirectUrl() {
28
+ return this.redirectUrl;
29
+ }
30
+ getUserService() {
31
+ return this.usersService;
32
+ }
33
+ getConfig() {
34
+ return this.config;
35
+ }
36
+ getClientName() {
37
+ return 'OpenID';
38
+ }
28
39
  constructor(options, config) {
29
40
  super(options, config);
30
41
  const { issuerUrl, clientId, clientSecret, ...additionalConfig } = config;
@@ -59,10 +70,7 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
59
70
  });
60
71
  });
61
72
  }
62
- generateCodeVerifier() {
63
- return generators.codeVerifier();
64
- }
65
- async generateAuthUrl(codeVerifier, prompt = false) {
73
+ async generateAuthUrl(codeVerifier, prompt = false, additionalParams) {
66
74
  try {
67
75
  const client = await this.client;
68
76
  const codeChallenge = generators.codeChallenge(codeVerifier);
@@ -77,25 +85,14 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
77
85
  // Some providers require state even with PKCE
78
86
  state: codeChallenge,
79
87
  nonce: codeChallenge,
88
+ ...additionalParams,
80
89
  });
81
90
  }
82
91
  catch (e) {
83
- throw handleError(e);
92
+ throw this.handleError(e);
84
93
  }
85
94
  }
86
- async fetchUserId(identifier) {
87
- const user = await this.knex
88
- .select('id')
89
- .from('directus_users')
90
- .whereRaw('LOWER(??) = ?', ['external_identifier', identifier.toLowerCase()])
91
- .first();
92
- return user?.id;
93
- }
94
- async getUserID(payload) {
95
- if (!payload['code'] || !payload['codeVerifier'] || !payload['state']) {
96
- logger.warn('[OpenID] No code, codeVerifier or state in payload');
97
- throw new InvalidCredentialsException();
98
- }
95
+ async getTokenSetAndUserInfo(payload) {
99
96
  let tokenSet;
100
97
  let userInfo;
101
98
  try {
@@ -111,11 +108,11 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
111
108
  }
112
109
  }
113
110
  catch (e) {
114
- throw handleError(e);
111
+ throw this.handleError(e);
115
112
  }
116
113
  // Flatten response to support dot indexes
117
114
  userInfo = flatten(userInfo);
118
- const { provider, identifierKey, allowPublicRegistration, requireVerifiedEmail } = this.config;
115
+ const { provider, identifierKey } = this.config;
119
116
  const email = userInfo['email'] ? String(userInfo['email']) : undefined;
120
117
  // Fallback to email if explicit identifier not found
121
118
  const identifier = userInfo[identifierKey ?? 'sub'] ? String(userInfo[identifierKey ?? 'sub']) : email;
@@ -132,102 +129,9 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
132
129
  role: this.config['defaultRoleId'],
133
130
  auth_data: tokenSet.refresh_token && JSON.stringify({ refreshToken: tokenSet.refresh_token }),
134
131
  };
135
- const userId = await this.fetchUserId(identifier);
136
- if (userId) {
137
- // Run hook so the end user has the chance to augment the
138
- // user that is about to be updated
139
- const updatedUserPayload = await emitter.emitFilter(`auth.update`, {}, {
140
- identifier,
141
- provider: this.config['provider'],
142
- providerPayload: { accessToken: tokenSet.access_token, userInfo },
143
- }, { database: getDatabase(), schema: this.schema, accountability: null });
144
- // Update user to update refresh_token and other properties that might have changed
145
- await this.usersService.updateOne(userId, updatedUserPayload);
146
- return userId;
147
- }
148
- const isEmailVerified = !requireVerifiedEmail || userInfo['email_verified'];
149
- // Is public registration allowed?
150
- if (!allowPublicRegistration || !isEmailVerified) {
151
- logger.warn(`[OpenID] User doesn't exist, and public registration not allowed for provider "${provider}"`);
152
- throw new InvalidCredentialsException();
153
- }
154
- // Run hook so the end user has the chance to augment the
155
- // user that is about to be created
156
- const updatedUserPayload = await emitter.emitFilter(`auth.create`, userPayload, {
157
- identifier,
158
- provider: this.config['provider'],
159
- providerPayload: { accessToken: tokenSet.access_token, userInfo },
160
- }, { database: getDatabase(), schema: this.schema, accountability: null });
161
- try {
162
- await this.usersService.createOne(updatedUserPayload);
163
- }
164
- catch (e) {
165
- if (e instanceof RecordNotUniqueException) {
166
- logger.warn(e, '[OpenID] Failed to register user. User not unique');
167
- throw new InvalidProviderException();
168
- }
169
- throw e;
170
- }
171
- return (await this.fetchUserId(identifier));
172
- }
173
- async login(user) {
174
- return this.refresh(user);
175
- }
176
- async refresh(user) {
177
- let authData = user.auth_data;
178
- if (typeof authData === 'string') {
179
- try {
180
- authData = parseJSON(authData);
181
- }
182
- catch {
183
- logger.warn(`[OpenID] Session data isn't valid JSON: ${authData}`);
184
- }
185
- }
186
- if (authData?.['refreshToken']) {
187
- try {
188
- const client = await this.client;
189
- const tokenSet = await client.refresh(authData['refreshToken']);
190
- // Update user refreshToken if provided
191
- if (tokenSet.refresh_token) {
192
- await this.usersService.updateOne(user.id, {
193
- auth_data: JSON.stringify({ refreshToken: tokenSet.refresh_token }),
194
- });
195
- }
196
- }
197
- catch (e) {
198
- if (e.error === 'invalid_grant') {
199
- // Remove invalid token from the user store
200
- await this.usersService.updateOne(user.id, {
201
- auth_data: null,
202
- });
203
- }
204
- throw handleError(e);
205
- }
206
- }
132
+ return [tokenSet, userInfo, userPayload];
207
133
  }
208
134
  }
209
- const handleError = (e) => {
210
- if (e instanceof errors.OPError) {
211
- if (e.error === 'invalid_grant') {
212
- // Invalid token
213
- logger.trace(e, `[OpenID] Invalid grant`);
214
- return new InvalidTokenException();
215
- }
216
- // Server response error
217
- logger.trace(e, `[OpenID] Unknown OP error`);
218
- return new ServiceUnavailableException('Service returned unexpected response', {
219
- service: 'openid',
220
- message: e.error_description,
221
- });
222
- }
223
- else if (e instanceof errors.RPError) {
224
- // Internal client error
225
- logger.trace(e, `[OpenID] Unknown RP error`);
226
- return new InvalidCredentialsException();
227
- }
228
- logger.trace(e, `[OpenID] Unknown error`);
229
- return e;
230
- };
231
135
  export function createOpenIDAuthRouter(providerName) {
232
136
  const router = Router();
233
137
  router.get('/', asyncHandler(async (req, res) => {
@@ -242,7 +146,10 @@ export function createOpenIDAuthRouter(providerName) {
242
146
  httpOnly: true,
243
147
  sameSite: 'lax',
244
148
  });
245
- return res.redirect(await provider.generateAuthUrl(codeVerifier, prompt));
149
+ const additionalParams = { ...req.query };
150
+ delete additionalParams['prompt'];
151
+ delete additionalParams['redirect'];
152
+ return res.redirect(await provider.generateAuthUrl(codeVerifier, prompt, additionalParams));
246
153
  }), respond);
247
154
  router.post('/callback', express.urlencoded({ extended: false }), (req, res) => {
248
155
  res.redirect(303, `./callback?${new URLSearchParams(req.body)}`);
package/dist/auth.js CHANGED
@@ -10,10 +10,11 @@ import { getSchema } from './utils/get-schema.js';
10
10
  const providerNames = toArray(env['AUTH_PROVIDERS']);
11
11
  const providers = new Map();
12
12
  export function getAuthProvider(provider) {
13
- if (!providers.has(provider)) {
13
+ const name = provider.toLowerCase();
14
+ if (!providers.has(name)) {
14
15
  throw new InvalidConfigException('Auth provider not configured', { provider });
15
16
  }
16
- return providers.get(provider);
17
+ return providers.get(name);
17
18
  }
18
19
  export async function registerAuthProviders() {
19
20
  const options = { knex: getDatabase(), schema: await getSchema() };
@@ -42,7 +43,7 @@ export async function registerAuthProviders() {
42
43
  logger.warn(`Invalid "${driver}" auth driver.`);
43
44
  return;
44
45
  }
45
- providers.set(name, provider);
46
+ providers.set(name.toLowerCase(), provider);
46
47
  });
47
48
  }
48
49
  function getProviderInstance(driver, options, config = {}) {
@@ -83,7 +83,7 @@ export class AuthenticationService {
83
83
  throw new InvalidCredentialsException();
84
84
  }
85
85
  }
86
- else if (user.provider !== providerName) {
86
+ else if (user.provider.toLowerCase() !== providerName.toLowerCase()) {
87
87
  await stall(STALL_TIME, timeStart);
88
88
  throw new InvalidProviderException();
89
89
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wbce-d9/api",
3
- "version": "9.28.0",
3
+ "version": "10.0.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": [
@@ -143,18 +143,18 @@
143
143
  "uuid-validate": "0.0.3",
144
144
  "vm2": "^3.9.19",
145
145
  "wellknown": "0.5.0",
146
- "@wbce-d9/constants": "9.26.3",
147
- "@wbce-d9/exceptions": "9.26.3",
148
146
  "@wbce-d9/app": "9.29.0",
147
+ "@wbce-d9/exceptions": "9.26.3",
149
148
  "@wbce-d9/schema": "9.26.3",
150
- "@wbce-d9/extensions-sdk": "9.26.5",
151
- "@wbce-d9/storage-driver-cloudinary": "9.26.5",
152
149
  "@wbce-d9/specs": "9.26.3",
153
- "@wbce-d9/storage-driver-azure": "9.26.5",
150
+ "@wbce-d9/extensions-sdk": "9.26.5",
154
151
  "@wbce-d9/storage": "9.26.3",
152
+ "@wbce-d9/constants": "9.26.3",
153
+ "@wbce-d9/storage-driver-azure": "9.26.5",
155
154
  "@wbce-d9/storage-driver-gcs": "9.26.5",
156
- "@wbce-d9/storage-driver-s3": "9.26.7",
157
155
  "@wbce-d9/storage-driver-local": "9.26.5",
156
+ "@wbce-d9/storage-driver-cloudinary": "9.26.5",
157
+ "@wbce-d9/storage-driver-s3": "9.26.7",
158
158
  "@wbce-d9/update-check": "9.26.3",
159
159
  "@wbce-d9/utils": "9.28.0"
160
160
  },