@jskit-ai/auth-provider-supabase-core 0.1.114 → 0.1.116
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/devAuthBootstrap.js +15 -98
- package/src/server/lib/index.js +0 -1
- package/src/server/lib/service.js +2 -2
- package/src/server/providers/AuthSupabaseServiceProvider.js +4 -47
- package/test/devAuthBootstrap.test.js +39 -8
- package/test/providerRuntime.test.js +14 -4
- package/src/server/lib/actions/auth.contributor.js +0 -21
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.116",
|
|
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.116",
|
|
87
|
+
"@jskit-ai/kernel": "0.1.119",
|
|
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.116",
|
|
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.116",
|
|
16
|
+
"@jskit-ai/kernel": "0.1.119",
|
|
17
17
|
"json-rest-schema": "1.x.x",
|
|
18
18
|
"jose": "^6.1.0",
|
|
19
19
|
"@supabase/supabase-js": "^2.57.4",
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
|
|
2
2
|
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
|
+
import {
|
|
4
|
+
assertDevAuthPolicy,
|
|
5
|
+
ensureDevAuthRuntimeAvailable,
|
|
6
|
+
resolveDevAuthPolicy
|
|
7
|
+
} from "@jskit-ai/auth-core/server/devAuth";
|
|
3
8
|
import { normalizeEmail } from "@jskit-ai/auth-core/server/utils";
|
|
4
9
|
import { loadJose, isExpiredJwtError } from "./authJwt.js";
|
|
5
10
|
|
|
@@ -9,20 +14,6 @@ const DEFAULT_DEV_AUTH_ACCESS_TTL_SECONDS = 60 * 60 * 12;
|
|
|
9
14
|
const DEFAULT_DEV_AUTH_REFRESH_TTL_SECONDS = 60 * 60 * 24 * 30;
|
|
10
15
|
const encoder = new TextEncoder();
|
|
11
16
|
|
|
12
|
-
function parseBoolean(value, fallback = false) {
|
|
13
|
-
const raw = String(value || "").trim().toLowerCase();
|
|
14
|
-
if (!raw) {
|
|
15
|
-
return fallback;
|
|
16
|
-
}
|
|
17
|
-
if (["1", "true", "yes", "on"].includes(raw)) {
|
|
18
|
-
return true;
|
|
19
|
-
}
|
|
20
|
-
if (["0", "false", "no", "off"].includes(raw)) {
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
return fallback;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
17
|
function normalizePositiveInteger(value, fallback) {
|
|
27
18
|
const parsed = Number.parseInt(String(value || "").trim(), 10);
|
|
28
19
|
if (Number.isInteger(parsed) && parsed > 0) {
|
|
@@ -31,66 +22,6 @@ function normalizePositiveInteger(value, fallback) {
|
|
|
31
22
|
return fallback;
|
|
32
23
|
}
|
|
33
24
|
|
|
34
|
-
function normalizeRequestHostname(request) {
|
|
35
|
-
const hostHeader = String(request?.headers?.host || "").trim();
|
|
36
|
-
if (!hostHeader) {
|
|
37
|
-
return "";
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const firstHost = hostHeader.split(",")[0]?.trim();
|
|
41
|
-
if (!firstHost) {
|
|
42
|
-
return "";
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
return new URL(`http://${firstHost}`).hostname.trim().toLowerCase();
|
|
47
|
-
} catch {
|
|
48
|
-
return firstHost
|
|
49
|
-
.replace(/^\[/, "")
|
|
50
|
-
.replace(/\]$/, "")
|
|
51
|
-
.split(":")[0]
|
|
52
|
-
.trim()
|
|
53
|
-
.toLowerCase();
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function resolveDirectRemoteAddress(request) {
|
|
58
|
-
return String(request?.socket?.remoteAddress || request?.raw?.socket?.remoteAddress || "").trim();
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function normalizeLoopbackIp(value) {
|
|
62
|
-
return String(value || "")
|
|
63
|
-
.trim()
|
|
64
|
-
.toLowerCase()
|
|
65
|
-
.replace(/^\[|\]$/g, "")
|
|
66
|
-
.replace(/^::ffff:/, "");
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function isLoopbackIp(value) {
|
|
70
|
-
const normalized = normalizeLoopbackIp(value);
|
|
71
|
-
return normalized === "::1" || normalized === "127.0.0.1" || normalized.startsWith("127.");
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function isLoopbackHostname(value) {
|
|
75
|
-
const normalized = String(value || "")
|
|
76
|
-
.trim()
|
|
77
|
-
.toLowerCase()
|
|
78
|
-
.replace(/^\[|\]$/g, "");
|
|
79
|
-
return (
|
|
80
|
-
normalized === "localhost" ||
|
|
81
|
-
normalized.endsWith(".localhost") ||
|
|
82
|
-
normalized === "::1" ||
|
|
83
|
-
normalized === "127.0.0.1"
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function isLocalDevAuthRequest(request) {
|
|
88
|
-
return (
|
|
89
|
-
isLoopbackIp(resolveDirectRemoteAddress(request)) &&
|
|
90
|
-
isLoopbackHostname(normalizeRequestHostname(request))
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
25
|
function isDevAuthToken(token) {
|
|
95
26
|
return String(token || "").trim().startsWith(DEV_AUTH_TOKEN_PREFIX);
|
|
96
27
|
}
|
|
@@ -107,10 +38,13 @@ function resolveDevAuthConfig({
|
|
|
107
38
|
accessTtlSeconds = DEFAULT_DEV_AUTH_ACCESS_TTL_SECONDS,
|
|
108
39
|
refreshTtlSeconds = DEFAULT_DEV_AUTH_REFRESH_TTL_SECONDS
|
|
109
40
|
} = {}) {
|
|
41
|
+
const policy = resolveDevAuthPolicy({
|
|
42
|
+
enabled,
|
|
43
|
+
nodeEnv,
|
|
44
|
+
secret
|
|
45
|
+
});
|
|
110
46
|
return Object.freeze({
|
|
111
|
-
|
|
112
|
-
secret: String(secret || "").trim(),
|
|
113
|
-
isProduction: String(nodeEnv || "development").trim().toLowerCase() === "production",
|
|
47
|
+
...policy,
|
|
114
48
|
jwtAudience: String(jwtAudience || "authenticated").trim() || "authenticated",
|
|
115
49
|
accessTtlSeconds: normalizePositiveInteger(accessTtlSeconds, DEFAULT_DEV_AUTH_ACCESS_TTL_SECONDS),
|
|
116
50
|
refreshTtlSeconds: normalizePositiveInteger(refreshTtlSeconds, DEFAULT_DEV_AUTH_REFRESH_TTL_SECONDS)
|
|
@@ -118,16 +52,10 @@ function resolveDevAuthConfig({
|
|
|
118
52
|
}
|
|
119
53
|
|
|
120
54
|
function assertDevAuthBootstrapConfig(config, { userProfilesRepository = null } = {}) {
|
|
55
|
+
assertDevAuthPolicy(config);
|
|
121
56
|
if (!config?.enabled) {
|
|
122
57
|
return;
|
|
123
58
|
}
|
|
124
|
-
|
|
125
|
-
if (config.isProduction) {
|
|
126
|
-
throw new Error("AUTH_DEV_BYPASS_ENABLED must not be enabled in production.");
|
|
127
|
-
}
|
|
128
|
-
if (!config.secret) {
|
|
129
|
-
throw new Error("AUTH_DEV_BYPASS_SECRET is required when AUTH_DEV_BYPASS_ENABLED=true.");
|
|
130
|
-
}
|
|
131
59
|
if (
|
|
132
60
|
!userProfilesRepository ||
|
|
133
61
|
typeof userProfilesRepository.findById !== "function" ||
|
|
@@ -139,18 +67,6 @@ function assertDevAuthBootstrapConfig(config, { userProfilesRepository = null }
|
|
|
139
67
|
}
|
|
140
68
|
}
|
|
141
69
|
|
|
142
|
-
function ensureDevAuthBootstrapAvailable(config, request) {
|
|
143
|
-
if (!config?.enabled || config?.isProduction) {
|
|
144
|
-
throw new AppError(404, "Not found.");
|
|
145
|
-
}
|
|
146
|
-
if (!config.secret) {
|
|
147
|
-
throw new AppError(500, "AUTH_DEV_BYPASS_SECRET is required when AUTH_DEV_BYPASS_ENABLED=true.");
|
|
148
|
-
}
|
|
149
|
-
if (!isLocalDevAuthRequest(request)) {
|
|
150
|
-
throw new AppError(403, "Dev auth bootstrap is only available from localhost.");
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
70
|
function buildProfileFromTokenClaims(payload) {
|
|
155
71
|
const id = normalizeRecordId(payload?.sub, { fallback: null });
|
|
156
72
|
const email = normalizeEmail(payload?.email || "");
|
|
@@ -322,7 +238,9 @@ async function authenticateDevAuthRequest(
|
|
|
322
238
|
return null;
|
|
323
239
|
}
|
|
324
240
|
|
|
325
|
-
|
|
241
|
+
try {
|
|
242
|
+
ensureDevAuthRuntimeAvailable(config, request);
|
|
243
|
+
} catch {
|
|
326
244
|
return {
|
|
327
245
|
authenticated: false,
|
|
328
246
|
clearSession: true,
|
|
@@ -391,7 +309,6 @@ export {
|
|
|
391
309
|
assertDevAuthBootstrapConfig,
|
|
392
310
|
authenticateDevAuthRequest,
|
|
393
311
|
createDevAuthSession,
|
|
394
|
-
ensureDevAuthBootstrapAvailable,
|
|
395
312
|
isDevAuthToken,
|
|
396
313
|
resolveDevAuthConfig,
|
|
397
314
|
resolveDevAuthProfile
|
package/src/server/lib/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createClient } from "@supabase/supabase-js";
|
|
2
2
|
import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
|
|
3
3
|
import { normalizeAuthCapabilities } from "@jskit-ai/auth-core/shared/authCapabilities";
|
|
4
|
+
import { ensureDevAuthExchangeAvailable } from "@jskit-ai/auth-core/server/devAuth";
|
|
4
5
|
import { normalizeAuthActor, normalizeAuthResult } from "@jskit-ai/auth-core/server/authActor";
|
|
5
6
|
import {
|
|
6
7
|
AUTH_METHOD_PASSWORD_ID,
|
|
@@ -56,7 +57,6 @@ import {
|
|
|
56
57
|
assertDevAuthBootstrapConfig,
|
|
57
58
|
authenticateDevAuthRequest,
|
|
58
59
|
createDevAuthSession,
|
|
59
|
-
ensureDevAuthBootstrapAvailable,
|
|
60
60
|
isDevAuthToken,
|
|
61
61
|
resolveDevAuthConfig,
|
|
62
62
|
resolveDevAuthProfile
|
|
@@ -1032,7 +1032,7 @@ function createService(options) {
|
|
|
1032
1032
|
}
|
|
1033
1033
|
|
|
1034
1034
|
async function devLoginAs(request, input = {}) {
|
|
1035
|
-
|
|
1035
|
+
ensureDevAuthExchangeAvailable(devAuthConfig, request);
|
|
1036
1036
|
const rawProfile = await resolveDevAuthProfile(input, {
|
|
1037
1037
|
userProfilesRepository,
|
|
1038
1038
|
validationError
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { resolveAllowedOriginsFromSurfaceDefinitions } from "@jskit-ai/kernel/shared/support/returnToPath";
|
|
2
|
-
import { withActionDefaults } from "@jskit-ai/kernel/shared/actions";
|
|
3
2
|
import { applyAuthServiceDecorators } from "@jskit-ai/auth-core/server/authServiceDecoratorRegistry";
|
|
4
3
|
import { normalizeEmail } from "@jskit-ai/auth-core/server/utils";
|
|
5
4
|
import { createService } from "../lib/service.js";
|
|
6
|
-
|
|
5
|
+
|
|
7
6
|
const PROFILE_MODE_PROVIDER = "provider";
|
|
8
7
|
const PROFILE_MODE_STANDALONE = "standalone";
|
|
9
8
|
const PROFILE_MODE_USERS = "users";
|
|
@@ -17,20 +16,6 @@ function splitCsv(value) {
|
|
|
17
16
|
.filter(Boolean);
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
function parseBoolean(value, fallback = false) {
|
|
21
|
-
const raw = String(value || "").trim().toLowerCase();
|
|
22
|
-
if (!raw) {
|
|
23
|
-
return fallback;
|
|
24
|
-
}
|
|
25
|
-
if (["1", "true", "yes", "on"].includes(raw)) {
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
28
|
-
if (["0", "false", "no", "off"].includes(raw)) {
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
return fallback;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
19
|
function normalizeRecord(value) {
|
|
35
20
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
36
21
|
}
|
|
@@ -103,18 +88,6 @@ function resolveAuthProfileMode(appConfig = {}) {
|
|
|
103
88
|
);
|
|
104
89
|
}
|
|
105
90
|
|
|
106
|
-
function isDevAuthBypassEnabledForRegistration(env) {
|
|
107
|
-
if (!isDevAuthBypassRequested(env)) {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return String(env?.NODE_ENV || "development").trim().toLowerCase() !== "production";
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function isDevAuthBypassRequested(env) {
|
|
115
|
-
return parseBoolean(env?.AUTH_DEV_BYPASS_ENABLED, false);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
91
|
function createProviderIdentityProfileSyncService({ authProviderId = "supabase" } = {}) {
|
|
119
92
|
return Object.freeze({
|
|
120
93
|
async findByIdentity() {
|
|
@@ -201,17 +174,13 @@ function isDeferredJsonRestBootGap(app, error) {
|
|
|
201
174
|
class AuthSupabaseServiceProvider {
|
|
202
175
|
static id = "auth.provider.supabase";
|
|
203
176
|
|
|
204
|
-
static dependsOn = ["runtime.actions"];
|
|
205
|
-
|
|
206
177
|
register(app) {
|
|
207
178
|
if (
|
|
208
179
|
!app ||
|
|
209
180
|
typeof app.singleton !== "function" ||
|
|
210
|
-
typeof app.has !== "function"
|
|
211
|
-
typeof app.actions !== "function" ||
|
|
212
|
-
typeof app.service !== "function"
|
|
181
|
+
typeof app.has !== "function"
|
|
213
182
|
) {
|
|
214
|
-
throw new Error("AuthSupabaseServiceProvider requires application singleton()/has()
|
|
183
|
+
throw new Error("AuthSupabaseServiceProvider requires application singleton()/has().");
|
|
215
184
|
}
|
|
216
185
|
|
|
217
186
|
assertSelectedAuthProvider(resolveRuntimeEnv(app));
|
|
@@ -227,7 +196,6 @@ class AuthSupabaseServiceProvider {
|
|
|
227
196
|
const authProvider = resolveAuthProviderConfig(env, appConfig);
|
|
228
197
|
const repositories = resolveOptionalRepositories(scope);
|
|
229
198
|
const userSettingsRepository = repositories.userSettingsRepository || null;
|
|
230
|
-
const devAuthBypassEnabled = parseBoolean(env.AUTH_DEV_BYPASS_ENABLED, false);
|
|
231
199
|
const authProfileMode = resolveAuthProfileMode(appConfig);
|
|
232
200
|
let userProfileSyncService = createProviderIdentityProfileSyncService({
|
|
233
201
|
authProviderId: authProvider.id
|
|
@@ -258,7 +226,7 @@ class AuthSupabaseServiceProvider {
|
|
|
258
226
|
userProfileSyncService,
|
|
259
227
|
profileProjectionEnabled,
|
|
260
228
|
userProfilesRepository: repositories.userProfilesRepository || null,
|
|
261
|
-
devAuthBypassEnabled,
|
|
229
|
+
devAuthBypassEnabled: env.AUTH_DEV_BYPASS_ENABLED,
|
|
262
230
|
devAuthBypassSecret: String(env.AUTH_DEV_BYPASS_SECRET || "").trim(),
|
|
263
231
|
devAuthAccessTtlSeconds: env.AUTH_DEV_ACCESS_TTL_SECONDS,
|
|
264
232
|
devAuthRefreshTtlSeconds: env.AUTH_DEV_REFRESH_TTL_SECONDS
|
|
@@ -266,17 +234,6 @@ class AuthSupabaseServiceProvider {
|
|
|
266
234
|
|
|
267
235
|
return applyAuthServiceDecorators(scope, authService);
|
|
268
236
|
});
|
|
269
|
-
|
|
270
|
-
if (isDevAuthBypassEnabledForRegistration(resolveRuntimeEnv(app))) {
|
|
271
|
-
app.actions(
|
|
272
|
-
withActionDefaults([devLoginAsAction], {
|
|
273
|
-
domain: "auth",
|
|
274
|
-
dependencies: {
|
|
275
|
-
authService: "authService"
|
|
276
|
-
}
|
|
277
|
-
})
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
237
|
}
|
|
281
238
|
|
|
282
239
|
boot(app) {
|
|
@@ -2,6 +2,8 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import { createService } from "../src/server/lib/index.js";
|
|
4
4
|
|
|
5
|
+
const DEV_AUTH_SECRET = "dev-bootstrap-secret";
|
|
6
|
+
|
|
5
7
|
function createProfile(overrides = {}) {
|
|
6
8
|
return {
|
|
7
9
|
id: "7",
|
|
@@ -64,6 +66,17 @@ function createLocalRequest(overrides = {}) {
|
|
|
64
66
|
};
|
|
65
67
|
}
|
|
66
68
|
|
|
69
|
+
function createDevAuthExchangeRequest(overrides = {}) {
|
|
70
|
+
const request = createLocalRequest(overrides);
|
|
71
|
+
return {
|
|
72
|
+
...request,
|
|
73
|
+
headers: {
|
|
74
|
+
...request.headers,
|
|
75
|
+
"x-jskit-dev-auth-secret": DEV_AUTH_SECRET
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
67
80
|
function createServiceFixture(overrides = {}) {
|
|
68
81
|
return createService({
|
|
69
82
|
authProvider: {
|
|
@@ -75,7 +88,7 @@ function createServiceFixture(overrides = {}) {
|
|
|
75
88
|
appPublicUrl: "http://localhost:5173",
|
|
76
89
|
nodeEnv: "development",
|
|
77
90
|
devAuthBypassEnabled: true,
|
|
78
|
-
devAuthBypassSecret:
|
|
91
|
+
devAuthBypassSecret: DEV_AUTH_SECRET,
|
|
79
92
|
userProfilesRepository: createUserProfilesRepository(),
|
|
80
93
|
userProfileSyncService: createUserProfileSyncService(),
|
|
81
94
|
...overrides
|
|
@@ -84,7 +97,7 @@ function createServiceFixture(overrides = {}) {
|
|
|
84
97
|
|
|
85
98
|
test("dev auth bootstrap can issue and authenticate a local session without Supabase", async () => {
|
|
86
99
|
const authService = createServiceFixture();
|
|
87
|
-
const loginRequest =
|
|
100
|
+
const loginRequest = createDevAuthExchangeRequest();
|
|
88
101
|
|
|
89
102
|
const loginResult = await authService.devLoginAs(loginRequest, {
|
|
90
103
|
userId: "7"
|
|
@@ -111,7 +124,7 @@ test("dev auth bootstrap can issue and authenticate a local session without Supa
|
|
|
111
124
|
|
|
112
125
|
test("dev auth bootstrap resolves security status without Supabase", async () => {
|
|
113
126
|
const authService = createServiceFixture();
|
|
114
|
-
const loginResult = await authService.devLoginAs(
|
|
127
|
+
const loginResult = await authService.devLoginAs(createDevAuthExchangeRequest(), {
|
|
115
128
|
userId: "7"
|
|
116
129
|
});
|
|
117
130
|
|
|
@@ -153,7 +166,7 @@ test("dev auth security status rejects invalid dev sessions without recovery-lin
|
|
|
153
166
|
test("dev auth bootstrap supports email lookup", async () => {
|
|
154
167
|
const authService = createServiceFixture();
|
|
155
168
|
|
|
156
|
-
const result = await authService.devLoginAs(
|
|
169
|
+
const result = await authService.devLoginAs(createDevAuthExchangeRequest(), {
|
|
157
170
|
email: "ADA@EXAMPLE.COM"
|
|
158
171
|
});
|
|
159
172
|
|
|
@@ -161,6 +174,24 @@ test("dev auth bootstrap supports email lookup", async () => {
|
|
|
161
174
|
assert.equal(result.profile.email, "ada@example.com");
|
|
162
175
|
});
|
|
163
176
|
|
|
177
|
+
test("dev auth bootstrap rejects a missing or incorrect exchange secret", async () => {
|
|
178
|
+
const authService = createServiceFixture();
|
|
179
|
+
|
|
180
|
+
await assert.rejects(
|
|
181
|
+
() => authService.devLoginAs(createLocalRequest(), { userId: "7" }),
|
|
182
|
+
/not authorized/
|
|
183
|
+
);
|
|
184
|
+
await assert.rejects(
|
|
185
|
+
() => authService.devLoginAs(createLocalRequest({
|
|
186
|
+
headers: {
|
|
187
|
+
host: "localhost:3000",
|
|
188
|
+
"x-jskit-dev-auth-secret": "wrong-secret"
|
|
189
|
+
}
|
|
190
|
+
}), { userId: "7" }),
|
|
191
|
+
/not authorized/
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
|
|
164
195
|
test("dev auth bootstrap canonicalizes producer profiles before returning them", async () => {
|
|
165
196
|
const rawProfile = createProfile({
|
|
166
197
|
email: " ADA@EXAMPLE.COM ",
|
|
@@ -177,7 +208,7 @@ test("dev auth bootstrap canonicalizes producer profiles before returning them",
|
|
|
177
208
|
userProfilesRepository: createUserProfilesRepository(rawProfile)
|
|
178
209
|
});
|
|
179
210
|
|
|
180
|
-
const loginResult = await authService.devLoginAs(
|
|
211
|
+
const loginResult = await authService.devLoginAs(createDevAuthExchangeRequest(), {
|
|
181
212
|
userId: "7"
|
|
182
213
|
});
|
|
183
214
|
|
|
@@ -195,14 +226,14 @@ test("dev auth bootstrap canonicalizes producer profiles before returning them",
|
|
|
195
226
|
|
|
196
227
|
test("dev auth bootstrap rejects non-local requests and clears leaked dev sessions", async () => {
|
|
197
228
|
const authService = createServiceFixture();
|
|
198
|
-
const issued = await authService.devLoginAs(
|
|
229
|
+
const issued = await authService.devLoginAs(createDevAuthExchangeRequest(), {
|
|
199
230
|
userId: "7"
|
|
200
231
|
});
|
|
201
232
|
|
|
202
233
|
await assert.rejects(
|
|
203
234
|
() =>
|
|
204
235
|
authService.devLoginAs(
|
|
205
|
-
|
|
236
|
+
createDevAuthExchangeRequest({
|
|
206
237
|
ip: "203.0.113.10",
|
|
207
238
|
hostname: "example.com",
|
|
208
239
|
headers: { host: "example.com" }
|
|
@@ -232,7 +263,7 @@ test("dev auth bootstrap does not trust forwarded localhost headers", async () =
|
|
|
232
263
|
await assert.rejects(
|
|
233
264
|
() =>
|
|
234
265
|
authService.devLoginAs(
|
|
235
|
-
|
|
266
|
+
createDevAuthExchangeRequest({
|
|
236
267
|
ip: "203.0.113.10",
|
|
237
268
|
socket: {
|
|
238
269
|
remoteAddress: "203.0.113.10"
|
|
@@ -37,7 +37,7 @@ function isBootFailureWithCause(error, pattern) {
|
|
|
37
37
|
);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
test("auth supabase provider defaults to provider profile mode and
|
|
40
|
+
test("auth supabase provider defaults to provider profile mode and composes with shared auth actions", async () => {
|
|
41
41
|
const app = createApplication();
|
|
42
42
|
app.instance("appConfig", createAppConfigFixture());
|
|
43
43
|
app.instance("jskit.env", {
|
|
@@ -126,7 +126,7 @@ test("auth supabase provider defaults to provider profile mode and contributes a
|
|
|
126
126
|
assert.equal(Array.isArray(definitions), true);
|
|
127
127
|
assert.equal(definitions.some((definition) => definition.id === "auth.login.password"), true);
|
|
128
128
|
assert.equal(definitions.some((definition) => definition.id === "auth.register.confirmation.resend"), true);
|
|
129
|
-
assert.equal(definitions.some((definition) => definition.id === "auth.dev.loginAs"),
|
|
129
|
+
assert.equal(definitions.some((definition) => definition.id === "auth.dev.loginAs"), true);
|
|
130
130
|
const sessionRead = definitions.find((definition) => definition.id === "auth.session.read");
|
|
131
131
|
assert.deepEqual(sessionRead?.surfaces, ["home", "console"]);
|
|
132
132
|
});
|
|
@@ -204,7 +204,12 @@ test("auth supabase provider reports password method toggle only with persistent
|
|
|
204
204
|
});
|
|
205
205
|
|
|
206
206
|
await app.start({
|
|
207
|
-
providers: [
|
|
207
|
+
providers: [
|
|
208
|
+
ActionRuntimeServiceProvider,
|
|
209
|
+
AuthSupabaseServiceProvider,
|
|
210
|
+
AuthProviderServiceProvider,
|
|
211
|
+
AuthActionsServiceProvider
|
|
212
|
+
]
|
|
208
213
|
});
|
|
209
214
|
|
|
210
215
|
const authService = app.make("authService");
|
|
@@ -348,7 +353,12 @@ test("auth supabase provider can boot dev auth without Supabase credentials", as
|
|
|
348
353
|
});
|
|
349
354
|
|
|
350
355
|
await app.start({
|
|
351
|
-
providers: [
|
|
356
|
+
providers: [
|
|
357
|
+
ActionRuntimeServiceProvider,
|
|
358
|
+
AuthSupabaseServiceProvider,
|
|
359
|
+
AuthProviderServiceProvider,
|
|
360
|
+
AuthActionsServiceProvider
|
|
361
|
+
]
|
|
352
362
|
});
|
|
353
363
|
|
|
354
364
|
const authService = app.make("authService");
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { authDevLoginAsCommand } from "@jskit-ai/auth-core/shared/commands";
|
|
2
|
-
import { requireRequestContext } from "@jskit-ai/auth-core/server/actions/auth.contributor";
|
|
3
|
-
|
|
4
|
-
const devLoginAsAction = Object.freeze({
|
|
5
|
-
id: "auth.dev.loginAs",
|
|
6
|
-
version: 1,
|
|
7
|
-
kind: "command",
|
|
8
|
-
channels: ["api", "internal"],
|
|
9
|
-
surfacesFrom: "enabled",
|
|
10
|
-
input: authDevLoginAsCommand.operation.body,
|
|
11
|
-
idempotency: "none",
|
|
12
|
-
audit: {
|
|
13
|
-
actionName: "auth.dev.loginAs"
|
|
14
|
-
},
|
|
15
|
-
observability: {},
|
|
16
|
-
async execute(input, context, deps) {
|
|
17
|
-
return deps.authService.devLoginAs(requireRequestContext(context, "auth.dev.loginAs"), input);
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
export { devLoginAsAction };
|