@jskit-ai/auth-provider-supabase-core 0.1.95 → 0.1.96
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 +3 -3
- package/package.json +3 -3
- package/src/server/lib/actions/auth.contributor.js +10 -2
- package/src/server/lib/authCookies.js +11 -1
- package/src/server/lib/passwordSecurityFlows.js +9 -1
- package/src/server/lib/service.js +103 -27
- package/test/auth.provider.supabase.test.js +160 -0
- package/test/devAuthBootstrap.test.js +41 -0
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.96",
|
|
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.96",
|
|
87
|
+
"@jskit-ai/kernel": "0.1.98",
|
|
88
88
|
"dotenv": "^16.4.5",
|
|
89
89
|
"@supabase/supabase-js": "^2.57.4",
|
|
90
90
|
"jose": "^6.1.0"
|
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.96",
|
|
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.96",
|
|
16
|
+
"@jskit-ai/kernel": "0.1.98",
|
|
17
17
|
"json-rest-schema": "1.x.x",
|
|
18
18
|
"jose": "^6.1.0",
|
|
19
19
|
"@supabase/supabase-js": "^2.57.4",
|
|
@@ -240,14 +240,22 @@ const authActionsAfterDevLogin = Object.freeze([
|
|
|
240
240
|
},
|
|
241
241
|
observability: {},
|
|
242
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
|
+
|
|
243
251
|
if (deps.authSessionEventsService && typeof deps.authSessionEventsService.notifySessionChanged === "function") {
|
|
244
252
|
await deps.authSessionEventsService.notifySessionChanged({
|
|
245
253
|
context
|
|
246
254
|
});
|
|
247
255
|
}
|
|
248
256
|
return {
|
|
249
|
-
ok:
|
|
250
|
-
clearSession:
|
|
257
|
+
ok: logoutResult?.ok !== false,
|
|
258
|
+
clearSession: logoutResult?.clearSession !== false
|
|
251
259
|
};
|
|
252
260
|
}
|
|
253
261
|
},
|
|
@@ -21,4 +21,14 @@ function cookieOptions(isProduction, maxAge) {
|
|
|
21
21
|
return options;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
function cookieClearOptions(isProduction) {
|
|
25
|
+
return [
|
|
26
|
+
cookieOptions(isProduction, 0),
|
|
27
|
+
{
|
|
28
|
+
...cookieOptions(isProduction, 0),
|
|
29
|
+
path: "/api"
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { safeRequestCookies, cookieOptions, cookieClearOptions };
|
|
@@ -32,7 +32,8 @@ function createPasswordSecurityFlows(deps) {
|
|
|
32
32
|
buildAuthMethodsStatusFromSupabaseUser,
|
|
33
33
|
buildSecurityStatusFromAuthMethodsStatus,
|
|
34
34
|
authMethodPasswordProvider,
|
|
35
|
-
buildAuthMethodsStatusFromProviderIds
|
|
35
|
+
buildAuthMethodsStatusFromProviderIds,
|
|
36
|
+
resolveDevAuthSecurityStatus
|
|
36
37
|
} = deps;
|
|
37
38
|
|
|
38
39
|
async function requestPasswordReset(payload) {
|
|
@@ -316,6 +317,13 @@ function createPasswordSecurityFlows(deps) {
|
|
|
316
317
|
return buildSecurityStatusFromAuthMethodsStatus(authMethodsStatus);
|
|
317
318
|
}
|
|
318
319
|
|
|
320
|
+
if (typeof resolveDevAuthSecurityStatus === "function") {
|
|
321
|
+
const devAuthSecurityStatus = await resolveDevAuthSecurityStatus(request);
|
|
322
|
+
if (devAuthSecurityStatus) {
|
|
323
|
+
return devAuthSecurityStatus;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
319
327
|
const current = await resolveCurrentAuthContext(request);
|
|
320
328
|
return buildSecurityStatusFromAuthMethodsStatus(current.authMethodsStatus);
|
|
321
329
|
}
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
findAuthMethodById,
|
|
43
43
|
findLinkedIdentityByProvider
|
|
44
44
|
} from "./authMethodStatus.js";
|
|
45
|
-
import { safeRequestCookies, cookieOptions } from "./authCookies.js";
|
|
45
|
+
import { safeRequestCookies, cookieOptions, cookieClearOptions } from "./authCookies.js";
|
|
46
46
|
import { loadJose, isExpiredJwtError, classifyJwtVerifyError } from "./authJwt.js";
|
|
47
47
|
import { buildDisabledPasswordSecret } from "./authSecrets.js";
|
|
48
48
|
import { createAccountFlows } from "./accountFlows.js";
|
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
authenticateDevAuthRequest,
|
|
56
56
|
createDevAuthSession,
|
|
57
57
|
ensureDevAuthBootstrapAvailable,
|
|
58
|
+
isDevAuthToken,
|
|
58
59
|
resolveDevAuthConfig,
|
|
59
60
|
resolveDevAuthProfile
|
|
60
61
|
} from "./devAuthBootstrap.js";
|
|
@@ -372,6 +373,39 @@ function createService(options) {
|
|
|
372
373
|
};
|
|
373
374
|
}
|
|
374
375
|
|
|
376
|
+
function resolveRequestSessionTokens(request) {
|
|
377
|
+
const cookies = safeRequestCookies(request);
|
|
378
|
+
return {
|
|
379
|
+
accessToken: String(cookies[ACCESS_TOKEN_COOKIE] || ""),
|
|
380
|
+
refreshToken: String(cookies[REFRESH_TOKEN_COOKIE] || "")
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async function authenticateDevAuthRequestFromCookies(request, tokens = resolveRequestSessionTokens(request)) {
|
|
385
|
+
const devAuthResult = await authenticateDevAuthRequest(
|
|
386
|
+
{
|
|
387
|
+
request,
|
|
388
|
+
accessToken: tokens.accessToken,
|
|
389
|
+
refreshToken: tokens.refreshToken
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
config: devAuthConfig,
|
|
393
|
+
userProfilesRepository
|
|
394
|
+
}
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
if (!devAuthResult || devAuthResult.authenticated !== true) {
|
|
398
|
+
return devAuthResult;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return {
|
|
402
|
+
...devAuthResult,
|
|
403
|
+
profile: requireAuthenticatedProfile(devAuthResult.profile, {
|
|
404
|
+
context: "dev auth profile"
|
|
405
|
+
})
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
375
409
|
function writeSessionCookies(reply, session) {
|
|
376
410
|
const accessToken = String(session?.access_token || "");
|
|
377
411
|
const refreshToken = String(session?.refresh_token || "");
|
|
@@ -387,9 +421,49 @@ function createService(options) {
|
|
|
387
421
|
}
|
|
388
422
|
|
|
389
423
|
function clearSessionCookies(reply) {
|
|
390
|
-
const clearOptions
|
|
391
|
-
|
|
392
|
-
|
|
424
|
+
for (const clearOptions of cookieClearOptions(isProduction)) {
|
|
425
|
+
reply.clearCookie(ACCESS_TOKEN_COOKIE, clearOptions);
|
|
426
|
+
reply.clearCookie(REFRESH_TOKEN_COOKIE, clearOptions);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async function logout(request) {
|
|
431
|
+
const cookies = safeRequestCookies(request);
|
|
432
|
+
const accessToken = String(cookies[ACCESS_TOKEN_COOKIE] || "").trim();
|
|
433
|
+
const refreshToken = String(cookies[REFRESH_TOKEN_COOKIE] || "").trim();
|
|
434
|
+
|
|
435
|
+
if (!accessToken && !refreshToken) {
|
|
436
|
+
return {
|
|
437
|
+
ok: true,
|
|
438
|
+
clearSession: true
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (isDevAuthToken(accessToken) || isDevAuthToken(refreshToken)) {
|
|
443
|
+
return {
|
|
444
|
+
ok: true,
|
|
445
|
+
clearSession: true
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
ensureConfigured();
|
|
450
|
+
const supabase = getSupabaseClient();
|
|
451
|
+
await setSessionFromRequestCookies(request, {
|
|
452
|
+
supabaseClient: supabase
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
const response = await supabase.auth.signOut({
|
|
456
|
+
scope: "local"
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
if (response.error) {
|
|
460
|
+
throw mapAuthError(response.error, Number(response.error?.status || 400));
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return {
|
|
464
|
+
ok: true,
|
|
465
|
+
clearSession: true
|
|
466
|
+
};
|
|
393
467
|
}
|
|
394
468
|
|
|
395
469
|
function requireSynchronizedProfile(profile) {
|
|
@@ -571,6 +645,25 @@ function createService(options) {
|
|
|
571
645
|
};
|
|
572
646
|
}
|
|
573
647
|
|
|
648
|
+
async function resolveDevAuthSecurityStatus(request) {
|
|
649
|
+
const devAuthResult = await authenticateDevAuthRequestFromCookies(request);
|
|
650
|
+
if (!devAuthResult) {
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (devAuthResult.authenticated !== true) {
|
|
655
|
+
throw new AppError(401, "Authentication required.");
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const passwordSignInPolicy = await resolvePasswordSignInPolicyForUserId(devAuthResult.profile.id);
|
|
659
|
+
const authMethodsStatus = buildAuthMethodsStatusFromProviderIds([AUTH_METHOD_PASSWORD_PROVIDER], {
|
|
660
|
+
...passwordSignInPolicy,
|
|
661
|
+
oauthProviders: authOAuthProviders
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
return buildSecurityStatusFromAuthMethodsStatus(authMethodsStatus);
|
|
665
|
+
}
|
|
666
|
+
|
|
574
667
|
const { register, resendRegisterConfirmation, login, requestOtpLogin, verifyOtpLogin, updateDisplayName } = createAccountFlows({
|
|
575
668
|
ensureConfigured,
|
|
576
669
|
validationError,
|
|
@@ -652,34 +745,15 @@ function createService(options) {
|
|
|
652
745
|
buildAuthMethodsStatusFromProviderIds(providerIds, {
|
|
653
746
|
...statusOptions,
|
|
654
747
|
oauthProviders: authOAuthProviders
|
|
655
|
-
})
|
|
748
|
+
}),
|
|
749
|
+
resolveDevAuthSecurityStatus
|
|
656
750
|
});
|
|
657
751
|
|
|
658
752
|
async function authenticateRequest(request) {
|
|
659
|
-
const
|
|
660
|
-
const accessToken = String(cookies[ACCESS_TOKEN_COOKIE] || "");
|
|
661
|
-
const refreshToken = String(cookies[REFRESH_TOKEN_COOKIE] || "");
|
|
753
|
+
const { accessToken, refreshToken } = resolveRequestSessionTokens(request);
|
|
662
754
|
|
|
663
|
-
const devAuthResult = await
|
|
664
|
-
{
|
|
665
|
-
request,
|
|
666
|
-
accessToken,
|
|
667
|
-
refreshToken
|
|
668
|
-
},
|
|
669
|
-
{
|
|
670
|
-
config: devAuthConfig,
|
|
671
|
-
userProfilesRepository
|
|
672
|
-
}
|
|
673
|
-
);
|
|
755
|
+
const devAuthResult = await authenticateDevAuthRequestFromCookies(request, { accessToken, refreshToken });
|
|
674
756
|
if (devAuthResult) {
|
|
675
|
-
if (devAuthResult.authenticated === true) {
|
|
676
|
-
return {
|
|
677
|
-
...devAuthResult,
|
|
678
|
-
profile: requireAuthenticatedProfile(devAuthResult.profile, {
|
|
679
|
-
context: "dev auth profile"
|
|
680
|
-
})
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
757
|
return devAuthResult;
|
|
684
758
|
}
|
|
685
759
|
|
|
@@ -860,6 +934,7 @@ function createService(options) {
|
|
|
860
934
|
setPasswordSignInEnabled,
|
|
861
935
|
unlinkProvider,
|
|
862
936
|
signOutOtherSessions,
|
|
937
|
+
logout,
|
|
863
938
|
getSecurityStatus,
|
|
864
939
|
getSettingsProfileAuthInfo,
|
|
865
940
|
getOAuthProviderCatalog,
|
|
@@ -909,6 +984,7 @@ const __testables = {
|
|
|
909
984
|
findLinkedIdentityByProvider,
|
|
910
985
|
safeRequestCookies,
|
|
911
986
|
cookieOptions,
|
|
987
|
+
cookieClearOptions,
|
|
912
988
|
isExpiredJwtError,
|
|
913
989
|
classifyJwtVerifyError
|
|
914
990
|
};
|
|
@@ -5,3 +5,163 @@ import * as authProviderSupabase from "../src/server/lib/index.js";
|
|
|
5
5
|
test("auth.provider.supabase exports required symbols", () => {
|
|
6
6
|
assert.equal(typeof authProviderSupabase.createService, "function");
|
|
7
7
|
});
|
|
8
|
+
|
|
9
|
+
function createServiceFixture(overrides = {}) {
|
|
10
|
+
return authProviderSupabase.createService({
|
|
11
|
+
authProvider: {
|
|
12
|
+
id: "supabase",
|
|
13
|
+
supabaseUrl: "",
|
|
14
|
+
supabasePublishableKey: ""
|
|
15
|
+
},
|
|
16
|
+
appPublicUrl: "http://localhost:5173",
|
|
17
|
+
nodeEnv: "development",
|
|
18
|
+
userProfileSyncService: {
|
|
19
|
+
async findByIdentity() {
|
|
20
|
+
return null;
|
|
21
|
+
},
|
|
22
|
+
async syncIdentityProfile(profile) {
|
|
23
|
+
return {
|
|
24
|
+
id: "1",
|
|
25
|
+
email: String(profile?.email || "ada@example.com"),
|
|
26
|
+
displayName: String(profile?.displayName || "Ada Example"),
|
|
27
|
+
authProvider: String(profile?.authProvider || "supabase"),
|
|
28
|
+
authProviderUserSid: String(profile?.authProviderUserSid || "supabase-user-1")
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
...overrides
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
test("clearSessionCookies clears root and API path session cookie variants", () => {
|
|
37
|
+
const authService = createServiceFixture();
|
|
38
|
+
const clearCalls = [];
|
|
39
|
+
const reply = {
|
|
40
|
+
clearCookie(name, options) {
|
|
41
|
+
clearCalls.push({
|
|
42
|
+
name,
|
|
43
|
+
options
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
authService.clearSessionCookies(reply);
|
|
49
|
+
|
|
50
|
+
assert.deepEqual(
|
|
51
|
+
clearCalls.map((call) => ({ name: call.name, path: call.options.path, maxAge: call.options.maxAge })),
|
|
52
|
+
[
|
|
53
|
+
{ name: "sb_access_token", path: "/", maxAge: 0 },
|
|
54
|
+
{ name: "sb_refresh_token", path: "/", maxAge: 0 },
|
|
55
|
+
{ name: "sb_access_token", path: "/api", maxAge: 0 },
|
|
56
|
+
{ name: "sb_refresh_token", path: "/api", maxAge: 0 }
|
|
57
|
+
]
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("clearSessionCookies preserves secure cookie clearing in production", () => {
|
|
62
|
+
const authService = createServiceFixture({
|
|
63
|
+
nodeEnv: "production"
|
|
64
|
+
});
|
|
65
|
+
const clearCalls = [];
|
|
66
|
+
const reply = {
|
|
67
|
+
clearCookie(name, options) {
|
|
68
|
+
clearCalls.push({
|
|
69
|
+
name,
|
|
70
|
+
options
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
authService.clearSessionCookies(reply);
|
|
76
|
+
|
|
77
|
+
assert.equal(clearCalls.length, 4);
|
|
78
|
+
assert.equal(clearCalls.every((call) => call.options.secure === true), true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("logout is local-only when no session cookies are present", async () => {
|
|
82
|
+
const authService = createServiceFixture();
|
|
83
|
+
|
|
84
|
+
const result = await authService.logout({
|
|
85
|
+
cookies: {}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
assert.deepEqual(result, {
|
|
89
|
+
ok: true,
|
|
90
|
+
clearSession: true
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("logout is local-only for dev auth cookies", async () => {
|
|
95
|
+
const authService = createServiceFixture();
|
|
96
|
+
|
|
97
|
+
const result = await authService.logout({
|
|
98
|
+
cookies: {
|
|
99
|
+
sb_access_token: "jskit-dev.invalid"
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
assert.deepEqual(result, {
|
|
104
|
+
ok: true,
|
|
105
|
+
clearSession: true
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("auth logout action delegates to provider logout and notifies session changes", async () => {
|
|
110
|
+
const action = authProviderSupabase
|
|
111
|
+
.buildAuthActions()
|
|
112
|
+
.find((definition) => definition.id === "auth.logout");
|
|
113
|
+
const request = {
|
|
114
|
+
id: "request-1"
|
|
115
|
+
};
|
|
116
|
+
const calls = [];
|
|
117
|
+
|
|
118
|
+
const result = await action.execute(
|
|
119
|
+
{},
|
|
120
|
+
{
|
|
121
|
+
requestMeta: {
|
|
122
|
+
request
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
authService: {
|
|
127
|
+
async logout(receivedRequest) {
|
|
128
|
+
calls.push({
|
|
129
|
+
type: "logout",
|
|
130
|
+
request: receivedRequest
|
|
131
|
+
});
|
|
132
|
+
return {
|
|
133
|
+
ok: true,
|
|
134
|
+
clearSession: true
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
authSessionEventsService: {
|
|
139
|
+
async notifySessionChanged(payload) {
|
|
140
|
+
calls.push({
|
|
141
|
+
type: "notify",
|
|
142
|
+
context: payload.context
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
assert.deepEqual(result, {
|
|
150
|
+
ok: true,
|
|
151
|
+
clearSession: true
|
|
152
|
+
});
|
|
153
|
+
assert.deepEqual(calls, [
|
|
154
|
+
{
|
|
155
|
+
type: "logout",
|
|
156
|
+
request
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
type: "notify",
|
|
160
|
+
context: {
|
|
161
|
+
requestMeta: {
|
|
162
|
+
request
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
]);
|
|
167
|
+
});
|
|
@@ -109,6 +109,47 @@ test("dev auth bootstrap can issue and authenticate a local session without Supa
|
|
|
109
109
|
assert.equal(authResult.session, null);
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
+
test("dev auth bootstrap resolves security status without Supabase", async () => {
|
|
113
|
+
const authService = createServiceFixture();
|
|
114
|
+
const loginResult = await authService.devLoginAs(createLocalRequest(), {
|
|
115
|
+
userId: "7"
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const securityStatus = await authService.getSecurityStatus(
|
|
119
|
+
createLocalRequest({
|
|
120
|
+
cookies: {
|
|
121
|
+
sb_access_token: loginResult.session.access_token,
|
|
122
|
+
sb_refresh_token: loginResult.session.refresh_token
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const passwordMethod = securityStatus.authMethods.find((method) => method.id === "password");
|
|
128
|
+
assert.equal(securityStatus.authPolicy.minimumEnabledMethods, 1);
|
|
129
|
+
assert.equal(passwordMethod?.configured, true);
|
|
130
|
+
assert.equal(passwordMethod?.enabled, true);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("dev auth security status rejects invalid dev sessions without recovery-link errors", async () => {
|
|
134
|
+
const authService = createServiceFixture();
|
|
135
|
+
|
|
136
|
+
await assert.rejects(
|
|
137
|
+
() =>
|
|
138
|
+
authService.getSecurityStatus(
|
|
139
|
+
createLocalRequest({
|
|
140
|
+
cookies: {
|
|
141
|
+
sb_access_token: "jskit-dev.invalid"
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
),
|
|
145
|
+
(error) => {
|
|
146
|
+
assert.equal(error.statusCode || error.status, 401);
|
|
147
|
+
assert.equal(error.message, "Authentication required.");
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
112
153
|
test("dev auth bootstrap supports email lookup", async () => {
|
|
113
154
|
const authService = createServiceFixture();
|
|
114
155
|
|