@jskit-ai/auth-provider-supabase-core 0.1.99 → 0.1.100
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/package.descriptor.mjs +6 -6
- package/package.json +3 -3
- package/src/server/lib/accountFlows.js +6 -2
- package/src/server/lib/actions/auth.contributor.js +3 -279
- package/src/server/lib/authMethodStatus.js +3 -23
- package/src/server/lib/index.js +1 -1
- package/src/server/lib/passwordSecurityFlows.js +14 -2
- package/src/server/lib/service.js +159 -29
- package/src/server/providers/AuthSupabaseServiceProvider.js +92 -126
- package/test/auth.provider.supabase.test.js +202 -51
- package/test/providerRuntime.test.js +146 -8
- package/src/server/lib/authSessionEventsService.js +0 -20
package/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
"packageVersion": 1,
|
|
3
3
|
"packageId": "@jskit-ai/auth-provider-supabase-core",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.100",
|
|
5
5
|
"kind": "runtime",
|
|
6
6
|
"options": {
|
|
7
7
|
"auth-supabase-url": {
|
|
@@ -83,8 +83,8 @@ export default Object.freeze({
|
|
|
83
83
|
"mutations": {
|
|
84
84
|
"dependencies": {
|
|
85
85
|
"runtime": {
|
|
86
|
-
"@jskit-ai/auth-core": "0.1.
|
|
87
|
-
"@jskit-ai/kernel": "0.1.
|
|
86
|
+
"@jskit-ai/auth-core": "0.1.100",
|
|
87
|
+
"@jskit-ai/kernel": "0.1.102",
|
|
88
88
|
"dotenv": "^16.4.5",
|
|
89
89
|
"@supabase/supabase-js": "^2.57.4",
|
|
90
90
|
"jose": "^6.1.0"
|
|
@@ -138,10 +138,10 @@ export default Object.freeze({
|
|
|
138
138
|
"file": "config/server.js",
|
|
139
139
|
"position": "bottom",
|
|
140
140
|
"skipIfContains": "config.auth.profileMode =",
|
|
141
|
-
"value": "\nconfig.auth ||= {};\nconfig.auth.profileMode = \"
|
|
142
|
-
"reason": "Use
|
|
141
|
+
"value": "\nconfig.auth ||= {};\nconfig.auth.profileMode = \"provider\";\n",
|
|
142
|
+
"reason": "Use provider-owned auth profiles until a users package enables users-backed sync.",
|
|
143
143
|
"category": "runtime-config",
|
|
144
|
-
"id": "auth-profile-mode-
|
|
144
|
+
"id": "auth-profile-mode-provider"
|
|
145
145
|
},
|
|
146
146
|
{
|
|
147
147
|
"op": "append-text",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/auth-provider-supabase-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.100",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"./client": "./src/client/index.js"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@jskit-ai/auth-core": "0.1.
|
|
16
|
-
"@jskit-ai/kernel": "0.1.
|
|
15
|
+
"@jskit-ai/auth-core": "0.1.100",
|
|
16
|
+
"@jskit-ai/kernel": "0.1.102",
|
|
17
17
|
"json-rest-schema": "1.x.x",
|
|
18
18
|
"jose": "^6.1.0",
|
|
19
19
|
"@supabase/supabase-js": "^2.57.4",
|
|
@@ -315,10 +315,14 @@ function createAccountFlows(deps) {
|
|
|
315
315
|
};
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
-
async function updateDisplayName(request,
|
|
318
|
+
async function updateDisplayName(request, payload = {}) {
|
|
319
319
|
ensureConfigured();
|
|
320
320
|
|
|
321
|
-
const normalizedDisplayName = String(
|
|
321
|
+
const normalizedDisplayName = String(
|
|
322
|
+
payload && typeof payload === "object" && !Array.isArray(payload)
|
|
323
|
+
? payload.displayName
|
|
324
|
+
: payload
|
|
325
|
+
).trim();
|
|
322
326
|
if (!normalizedDisplayName) {
|
|
323
327
|
throw validationError({
|
|
324
328
|
displayName: "Display name is required."
|
|
@@ -1,49 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
emptyInputValidator
|
|
4
|
-
} from "@jskit-ai/kernel/shared/actions/actionContributorHelpers";
|
|
5
|
-
import {
|
|
6
|
-
composeSchemaDefinitions
|
|
7
|
-
} from "@jskit-ai/kernel/shared/validators";
|
|
8
|
-
import { deepFreeze } from "@jskit-ai/kernel/shared/support/deepFreeze";
|
|
9
|
-
import {
|
|
10
|
-
authRegisterCommand,
|
|
11
|
-
authRegisterConfirmationResendCommand,
|
|
12
|
-
authLoginPasswordCommand,
|
|
13
|
-
authLoginOtpRequestCommand,
|
|
14
|
-
authLoginOtpVerifyCommand,
|
|
15
|
-
authLoginOAuthStartCommand,
|
|
16
|
-
authLoginOAuthCompleteCommand,
|
|
17
|
-
authDevLoginAsCommand,
|
|
18
|
-
authPasswordResetRequestCommand,
|
|
19
|
-
authPasswordRecoveryCompleteCommand,
|
|
20
|
-
authPasswordResetCommand
|
|
21
|
-
} from "@jskit-ai/auth-core/shared/commands";
|
|
22
|
-
|
|
23
|
-
const authLoginOAuthStartInput = composeSchemaDefinitions([
|
|
24
|
-
authLoginOAuthStartCommand.operation.params,
|
|
25
|
-
authLoginOAuthStartCommand.operation.query
|
|
26
|
-
], {
|
|
27
|
-
mode: "patch",
|
|
28
|
-
context: "authContributor.authLoginOAuthStartInput"
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
const authLogoutOutput = deepFreeze({
|
|
32
|
-
schema: createSchema({
|
|
33
|
-
ok: { type: "boolean", required: true },
|
|
34
|
-
clearSession: { type: "boolean", required: true }
|
|
35
|
-
}),
|
|
36
|
-
mode: "replace"
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
function requireRequestContext(context, actionId) {
|
|
40
|
-
const request = context?.requestMeta?.request || null;
|
|
41
|
-
if (request) {
|
|
42
|
-
return request;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
throw new Error(`${actionId} requires request context.`);
|
|
46
|
-
}
|
|
1
|
+
import { authDevLoginAsCommand } from "@jskit-ai/auth-core/shared/commands";
|
|
2
|
+
import { requireRequestContext } from "@jskit-ai/auth-core/server/actions/auth.contributor";
|
|
47
3
|
|
|
48
4
|
const devLoginAsAction = Object.freeze({
|
|
49
5
|
id: "auth.dev.loginAs",
|
|
@@ -62,236 +18,4 @@ const devLoginAsAction = Object.freeze({
|
|
|
62
18
|
}
|
|
63
19
|
});
|
|
64
20
|
|
|
65
|
-
|
|
66
|
-
{
|
|
67
|
-
id: "auth.register",
|
|
68
|
-
version: 1,
|
|
69
|
-
kind: "command",
|
|
70
|
-
channels: ["api", "internal"],
|
|
71
|
-
surfacesFrom: "enabled",
|
|
72
|
-
input: authRegisterCommand.operation.body,
|
|
73
|
-
idempotency: "none",
|
|
74
|
-
audit: {
|
|
75
|
-
actionName: "auth.register"
|
|
76
|
-
},
|
|
77
|
-
observability: {},
|
|
78
|
-
async execute(input, _context, deps) {
|
|
79
|
-
return deps.authService.register(input);
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
id: "auth.register.confirmation.resend",
|
|
84
|
-
version: 1,
|
|
85
|
-
kind: "command",
|
|
86
|
-
channels: ["api", "internal"],
|
|
87
|
-
surfacesFrom: "enabled",
|
|
88
|
-
input: authRegisterConfirmationResendCommand.operation.body,
|
|
89
|
-
idempotency: "none",
|
|
90
|
-
audit: {
|
|
91
|
-
actionName: "auth.register.confirmation.resend"
|
|
92
|
-
},
|
|
93
|
-
observability: {},
|
|
94
|
-
async execute(input, _context, deps) {
|
|
95
|
-
return deps.authService.resendRegisterConfirmation(input);
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
id: "auth.login.password",
|
|
100
|
-
version: 1,
|
|
101
|
-
kind: "command",
|
|
102
|
-
channels: ["api", "internal"],
|
|
103
|
-
surfacesFrom: "enabled",
|
|
104
|
-
input: authLoginPasswordCommand.operation.body,
|
|
105
|
-
idempotency: "none",
|
|
106
|
-
audit: {
|
|
107
|
-
actionName: "auth.login.password"
|
|
108
|
-
},
|
|
109
|
-
observability: {},
|
|
110
|
-
async execute(input, _context, deps) {
|
|
111
|
-
return deps.authService.login(input);
|
|
112
|
-
}
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
id: "auth.login.otp.request",
|
|
116
|
-
version: 1,
|
|
117
|
-
kind: "command",
|
|
118
|
-
channels: ["api", "internal"],
|
|
119
|
-
surfacesFrom: "enabled",
|
|
120
|
-
input: authLoginOtpRequestCommand.operation.body,
|
|
121
|
-
idempotency: "none",
|
|
122
|
-
audit: {
|
|
123
|
-
actionName: "auth.login.otp.request"
|
|
124
|
-
},
|
|
125
|
-
observability: {},
|
|
126
|
-
async execute(input, _context, deps) {
|
|
127
|
-
return deps.authService.requestOtpLogin(input);
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
id: "auth.login.otp.verify",
|
|
132
|
-
version: 1,
|
|
133
|
-
kind: "command",
|
|
134
|
-
channels: ["api", "internal"],
|
|
135
|
-
surfacesFrom: "enabled",
|
|
136
|
-
input: authLoginOtpVerifyCommand.operation.body,
|
|
137
|
-
idempotency: "none",
|
|
138
|
-
audit: {
|
|
139
|
-
actionName: "auth.login.otp.verify"
|
|
140
|
-
},
|
|
141
|
-
observability: {},
|
|
142
|
-
async execute(input, _context, deps) {
|
|
143
|
-
return deps.authService.verifyOtpLogin(input);
|
|
144
|
-
}
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
id: "auth.login.oauth.start",
|
|
148
|
-
version: 1,
|
|
149
|
-
kind: "command",
|
|
150
|
-
channels: ["api", "internal"],
|
|
151
|
-
surfacesFrom: "enabled",
|
|
152
|
-
input: authLoginOAuthStartInput,
|
|
153
|
-
idempotency: "none",
|
|
154
|
-
audit: {
|
|
155
|
-
actionName: "auth.login.oauth.start"
|
|
156
|
-
},
|
|
157
|
-
observability: {},
|
|
158
|
-
async execute(input, _context, deps) {
|
|
159
|
-
return deps.authService.oauthStart(input);
|
|
160
|
-
}
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
id: "auth.login.oauth.complete",
|
|
164
|
-
version: 1,
|
|
165
|
-
kind: "command",
|
|
166
|
-
channels: ["api", "internal"],
|
|
167
|
-
surfacesFrom: "enabled",
|
|
168
|
-
input: authLoginOAuthCompleteCommand.operation.body,
|
|
169
|
-
idempotency: "none",
|
|
170
|
-
audit: {
|
|
171
|
-
actionName: "auth.login.oauth.complete"
|
|
172
|
-
},
|
|
173
|
-
observability: {},
|
|
174
|
-
async execute(input, _context, deps) {
|
|
175
|
-
return deps.authService.oauthComplete(input);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
]);
|
|
179
|
-
|
|
180
|
-
const authActionsAfterDevLogin = Object.freeze([
|
|
181
|
-
{
|
|
182
|
-
id: "auth.password.reset.request",
|
|
183
|
-
version: 1,
|
|
184
|
-
kind: "command",
|
|
185
|
-
channels: ["api", "internal"],
|
|
186
|
-
surfacesFrom: "enabled",
|
|
187
|
-
input: authPasswordResetRequestCommand.operation.body,
|
|
188
|
-
idempotency: "none",
|
|
189
|
-
audit: {
|
|
190
|
-
actionName: "auth.password.reset.request"
|
|
191
|
-
},
|
|
192
|
-
observability: {},
|
|
193
|
-
async execute(input, _context, deps) {
|
|
194
|
-
return deps.authService.requestPasswordReset(input);
|
|
195
|
-
}
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
id: "auth.password.recovery.complete",
|
|
199
|
-
version: 1,
|
|
200
|
-
kind: "command",
|
|
201
|
-
channels: ["api", "internal"],
|
|
202
|
-
surfacesFrom: "enabled",
|
|
203
|
-
input: authPasswordRecoveryCompleteCommand.operation.body,
|
|
204
|
-
idempotency: "none",
|
|
205
|
-
audit: {
|
|
206
|
-
actionName: "auth.password.recovery.complete"
|
|
207
|
-
},
|
|
208
|
-
observability: {},
|
|
209
|
-
async execute(input, _context, deps) {
|
|
210
|
-
return deps.authService.completePasswordRecovery(input);
|
|
211
|
-
}
|
|
212
|
-
},
|
|
213
|
-
{
|
|
214
|
-
id: "auth.password.reset",
|
|
215
|
-
version: 1,
|
|
216
|
-
kind: "command",
|
|
217
|
-
channels: ["api", "internal"],
|
|
218
|
-
surfacesFrom: "enabled",
|
|
219
|
-
input: authPasswordResetCommand.operation.body,
|
|
220
|
-
idempotency: "none",
|
|
221
|
-
audit: {
|
|
222
|
-
actionName: "auth.password.reset"
|
|
223
|
-
},
|
|
224
|
-
observability: {},
|
|
225
|
-
async execute(input, context, deps) {
|
|
226
|
-
return deps.authService.resetPassword(requireRequestContext(context, "auth.password.reset"), input);
|
|
227
|
-
}
|
|
228
|
-
},
|
|
229
|
-
{
|
|
230
|
-
id: "auth.logout",
|
|
231
|
-
version: 1,
|
|
232
|
-
kind: "command",
|
|
233
|
-
channels: ["api", "automation", "internal"],
|
|
234
|
-
surfacesFrom: "enabled",
|
|
235
|
-
input: emptyInputValidator,
|
|
236
|
-
output: authLogoutOutput,
|
|
237
|
-
idempotency: "none",
|
|
238
|
-
audit: {
|
|
239
|
-
actionName: "auth.logout"
|
|
240
|
-
},
|
|
241
|
-
observability: {},
|
|
242
|
-
async execute(_input, context, deps) {
|
|
243
|
-
let logoutResult = {
|
|
244
|
-
ok: true,
|
|
245
|
-
clearSession: true
|
|
246
|
-
};
|
|
247
|
-
if (deps.authService && typeof deps.authService.logout === "function") {
|
|
248
|
-
logoutResult = await deps.authService.logout(requireRequestContext(context, "auth.logout"));
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (deps.authSessionEventsService && typeof deps.authSessionEventsService.notifySessionChanged === "function") {
|
|
252
|
-
await deps.authSessionEventsService.notifySessionChanged({
|
|
253
|
-
context
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
return {
|
|
257
|
-
ok: logoutResult?.ok !== false,
|
|
258
|
-
clearSession: logoutResult?.clearSession !== false
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
},
|
|
262
|
-
{
|
|
263
|
-
id: "auth.session.read",
|
|
264
|
-
version: 1,
|
|
265
|
-
kind: "query",
|
|
266
|
-
channels: ["api", "internal"],
|
|
267
|
-
surfacesFrom: "enabled",
|
|
268
|
-
input: emptyInputValidator,
|
|
269
|
-
idempotency: "none",
|
|
270
|
-
audit: {
|
|
271
|
-
actionName: "auth.session.read"
|
|
272
|
-
},
|
|
273
|
-
observability: {},
|
|
274
|
-
async execute(_input, context, deps) {
|
|
275
|
-
return deps.authService.authenticateRequest(requireRequestContext(context, "auth.session.read"));
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
]);
|
|
279
|
-
|
|
280
|
-
const baseAuthActions = Object.freeze([
|
|
281
|
-
...authActionsBeforeDevLogin,
|
|
282
|
-
...authActionsAfterDevLogin
|
|
283
|
-
]);
|
|
284
|
-
|
|
285
|
-
function buildAuthActions({ includeDevLoginAs = false } = {}) {
|
|
286
|
-
if (!includeDevLoginAs) {
|
|
287
|
-
return baseAuthActions;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return Object.freeze([
|
|
291
|
-
...authActionsBeforeDevLogin,
|
|
292
|
-
devLoginAsAction,
|
|
293
|
-
...authActionsAfterDevLogin
|
|
294
|
-
]);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
export { baseAuthActions, buildAuthActions, devLoginAsAction };
|
|
21
|
+
export { devLoginAsAction };
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
buildAuthMethodDefinitions,
|
|
11
11
|
buildOAuthMethodId
|
|
12
12
|
} from "@jskit-ai/auth-core/shared/authMethods";
|
|
13
|
+
import { buildSecurityStatusFromAuthMethodsStatus as buildCoreSecurityStatusFromAuthMethodsStatus } from "@jskit-ai/auth-core/shared/authSecurityStatus";
|
|
13
14
|
|
|
14
15
|
function normalizeIdentityProviderId(value) {
|
|
15
16
|
return String(value || "")
|
|
@@ -168,29 +169,8 @@ function buildAuthMethodsStatusFromSupabaseUser(user, options = {}) {
|
|
|
168
169
|
return buildAuthMethodsStatusFromProviderIds(collectProviderIdsFromSupabaseUser(user), options);
|
|
169
170
|
}
|
|
170
171
|
|
|
171
|
-
function buildSecurityStatusFromAuthMethodsStatus(authMethodsStatus) {
|
|
172
|
-
|
|
173
|
-
let enabledMethodsCount = 0;
|
|
174
|
-
|
|
175
|
-
const storedCount = Number(authMethodsStatus?.enabledMethodsCount);
|
|
176
|
-
if (Number.isFinite(storedCount)) {
|
|
177
|
-
enabledMethodsCount = storedCount;
|
|
178
|
-
} else if (Array.isArray(authMethodsStatus?.methods)) {
|
|
179
|
-
enabledMethodsCount = countEnabledMethods(authMethodsStatus.methods);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return {
|
|
183
|
-
mfa: {
|
|
184
|
-
status: "not_enabled",
|
|
185
|
-
enrolled: false,
|
|
186
|
-
methods: []
|
|
187
|
-
},
|
|
188
|
-
authPolicy: {
|
|
189
|
-
minimumEnabledMethods,
|
|
190
|
-
enabledMethodsCount
|
|
191
|
-
},
|
|
192
|
-
authMethods: Array.isArray(authMethodsStatus?.methods) ? authMethodsStatus.methods : []
|
|
193
|
-
};
|
|
172
|
+
function buildSecurityStatusFromAuthMethodsStatus(authMethodsStatus, options = {}) {
|
|
173
|
+
return buildCoreSecurityStatusFromAuthMethodsStatus(authMethodsStatus, options);
|
|
194
174
|
}
|
|
195
175
|
|
|
196
176
|
function findAuthMethodById(authMethodsStatus, methodId) {
|
package/src/server/lib/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { createService, __testables } from "./service.js";
|
|
2
|
-
export {
|
|
2
|
+
export { devLoginAsAction } from "./actions/auth.contributor.js";
|
|
@@ -18,6 +18,7 @@ function createPasswordSecurityFlows(deps) {
|
|
|
18
18
|
mapRecoveryError,
|
|
19
19
|
syncProfileFromSupabaseUser,
|
|
20
20
|
setSessionFromRequestCookies,
|
|
21
|
+
setRecoverySessionFromRequestCookies,
|
|
21
22
|
resolvePasswordSignInPolicyForUserId,
|
|
22
23
|
mapPasswordUpdateError,
|
|
23
24
|
setPasswordSetupRequiredForUserId,
|
|
@@ -130,7 +131,10 @@ function createPasswordSecurityFlows(deps) {
|
|
|
130
131
|
|
|
131
132
|
return {
|
|
132
133
|
profile,
|
|
133
|
-
session
|
|
134
|
+
session: {
|
|
135
|
+
...session,
|
|
136
|
+
purpose: "recovery"
|
|
137
|
+
}
|
|
134
138
|
};
|
|
135
139
|
}
|
|
136
140
|
|
|
@@ -144,7 +148,7 @@ function createPasswordSecurityFlows(deps) {
|
|
|
144
148
|
const parsed = result.validatedObject;
|
|
145
149
|
|
|
146
150
|
const supabase = getSupabaseClient();
|
|
147
|
-
const sessionResponse = await
|
|
151
|
+
const sessionResponse = await setRecoverySessionFromRequestCookies(request, {
|
|
148
152
|
supabaseClient: supabase
|
|
149
153
|
});
|
|
150
154
|
const profile = await syncProfileFromSupabaseUser(
|
|
@@ -169,6 +173,14 @@ function createPasswordSecurityFlows(deps) {
|
|
|
169
173
|
|
|
170
174
|
const updatedProfile = await syncProfileFromSupabaseUser(user, user.email);
|
|
171
175
|
await setPasswordSetupRequiredForUserId(updatedProfile.id, false);
|
|
176
|
+
|
|
177
|
+
const signOutResponse = await supabase.auth.signOut({
|
|
178
|
+
scope: "local"
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
if (signOutResponse.error) {
|
|
182
|
+
throw mapAuthError(signOutResponse.error, Number(signOutResponse.error?.status || 400));
|
|
183
|
+
}
|
|
172
184
|
}
|
|
173
185
|
|
|
174
186
|
async function changePassword(request, payload) {
|