@jskit-ai/auth-provider-local-core 0.1.60 → 0.1.62
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.json +20 -78
- package/src/server/providers/AuthLocalProvider.js +129 -0
- package/test/packageMetadata.test.js +5 -16
- package/test/providerRuntime.test.js +89 -165
- package/src/server/providers/AuthLocalServiceProvider.js +0 -233
- package/src/server/providers/AuthProviderServiceProvider.js +0 -7
package/package.json
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/auth-provider-local-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.62",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
7
7
|
},
|
|
8
8
|
"exports": {
|
|
9
|
-
"./server/providers/
|
|
10
|
-
"./server/providers/AuthProviderServiceProvider": "./src/server/providers/AuthProviderServiceProvider.js",
|
|
9
|
+
"./server/providers/AuthLocalProvider": "./src/server/providers/AuthLocalProvider.js",
|
|
11
10
|
"./server/lib/index": "./src/server/lib/index.js"
|
|
12
11
|
},
|
|
13
12
|
"dependencies": {
|
|
14
|
-
"@jskit-ai/auth-core": "0.1.
|
|
15
|
-
"@jskit-ai/kernel": "0.1.
|
|
13
|
+
"@jskit-ai/auth-core": "0.1.159",
|
|
14
|
+
"@jskit-ai/kernel": "0.1.161",
|
|
16
15
|
"nodemailer": "^9.0.3"
|
|
17
16
|
},
|
|
18
17
|
"description": "Local auth provider with a file backend default and no database requirement.",
|
|
@@ -20,24 +19,22 @@
|
|
|
20
19
|
"kind": "runtime",
|
|
21
20
|
"capabilities": {
|
|
22
21
|
"provides": [
|
|
23
|
-
"auth.
|
|
24
|
-
"auth.provider"
|
|
22
|
+
"auth.service"
|
|
25
23
|
],
|
|
26
24
|
"requires": [
|
|
27
|
-
"auth.
|
|
25
|
+
"auth.extensions",
|
|
26
|
+
"runtime.app-root",
|
|
27
|
+
"runtime.env",
|
|
28
|
+
"runtime.logger"
|
|
28
29
|
]
|
|
29
30
|
},
|
|
30
31
|
"runtime": {
|
|
31
32
|
"server": {
|
|
32
|
-
"providerEntrypoint": "src/server/providers/
|
|
33
|
+
"providerEntrypoint": "src/server/providers/AuthLocalProvider.js",
|
|
33
34
|
"providers": [
|
|
34
35
|
{
|
|
35
|
-
"entrypoint": "src/server/providers/
|
|
36
|
-
"export": "
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
"entrypoint": "src/server/providers/AuthProviderServiceProvider.js",
|
|
40
|
-
"export": "AuthProviderServiceProvider"
|
|
36
|
+
"entrypoint": "src/server/providers/AuthLocalProvider.js",
|
|
37
|
+
"export": "AuthLocalProvider"
|
|
41
38
|
}
|
|
42
39
|
]
|
|
43
40
|
}
|
|
@@ -46,76 +43,21 @@
|
|
|
46
43
|
"apiSummary": {
|
|
47
44
|
"surfaces": [
|
|
48
45
|
{
|
|
49
|
-
"subpath": "./server/providers/
|
|
50
|
-
"summary": "
|
|
46
|
+
"subpath": "./server/providers/AuthLocalProvider",
|
|
47
|
+
"summary": "Provides the selected local authentication service through explicit runtime capabilities."
|
|
51
48
|
},
|
|
52
49
|
{
|
|
53
50
|
"subpath": "./server/lib/index",
|
|
54
51
|
"summary": "Exports local auth service, file backend helpers, password strategy helpers, and local auth register hook decorators."
|
|
55
52
|
}
|
|
56
53
|
],
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"client": []
|
|
64
|
-
}
|
|
54
|
+
"extensionCapabilities": [
|
|
55
|
+
"auth.invitation-context",
|
|
56
|
+
"auth.local.backend",
|
|
57
|
+
"auth.local.password-strategy",
|
|
58
|
+
"auth.profile.projector"
|
|
59
|
+
]
|
|
65
60
|
}
|
|
66
|
-
},
|
|
67
|
-
"ci": {
|
|
68
|
-
"environment": {
|
|
69
|
-
"AUTH_PROVIDER": "local"
|
|
70
|
-
},
|
|
71
|
-
"services": [],
|
|
72
|
-
"steps": []
|
|
73
|
-
},
|
|
74
|
-
"mutations": {
|
|
75
|
-
"dependencies": {
|
|
76
|
-
"runtime": {
|
|
77
|
-
"@jskit-ai/auth-core": "0.1.157",
|
|
78
|
-
"@jskit-ai/kernel": "0.1.159",
|
|
79
|
-
"nodemailer": "^9.0.3",
|
|
80
|
-
"dotenv": "^16.4.5"
|
|
81
|
-
},
|
|
82
|
-
"dev": {}
|
|
83
|
-
},
|
|
84
|
-
"packageJson": {
|
|
85
|
-
"scripts": {}
|
|
86
|
-
},
|
|
87
|
-
"procfile": {},
|
|
88
|
-
"files": [],
|
|
89
|
-
"text": [
|
|
90
|
-
{
|
|
91
|
-
"file": ".env",
|
|
92
|
-
"op": "upsert-env",
|
|
93
|
-
"key": "AUTH_PROVIDER",
|
|
94
|
-
"value": "local",
|
|
95
|
-
"reason": "Select local auth as the auth provider.",
|
|
96
|
-
"category": "runtime-config",
|
|
97
|
-
"id": "auth-provider"
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
"file": ".env",
|
|
101
|
-
"op": "upsert-env",
|
|
102
|
-
"key": "AUTH_LOCAL_BACKEND",
|
|
103
|
-
"value": "file",
|
|
104
|
-
"reason": "Use the built-in local file auth backend.",
|
|
105
|
-
"category": "runtime-config",
|
|
106
|
-
"id": "auth-local-backend"
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
"file": ".gitignore",
|
|
110
|
-
"op": "append-text",
|
|
111
|
-
"position": "bottom",
|
|
112
|
-
"skipIfContains": ".jskit/auth/",
|
|
113
|
-
"value": "\n.jskit/auth/\n",
|
|
114
|
-
"reason": "Ignore local auth runtime state.",
|
|
115
|
-
"category": "runtime-state",
|
|
116
|
-
"id": "auth-local-store-gitignore"
|
|
117
|
-
}
|
|
118
|
-
]
|
|
119
61
|
}
|
|
120
62
|
}
|
|
121
63
|
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { parseBooleanFlag } from "@jskit-ai/auth-core/server/booleanFlag";
|
|
2
|
+
import { assertDevAuthPolicy, resolveDevAuthPolicyFromEnv } from "@jskit-ai/auth-core/server/devAuth";
|
|
3
|
+
import {
|
|
4
|
+
attachDeferredAuthService,
|
|
5
|
+
createDeferredAuthService
|
|
6
|
+
} from "@jskit-ai/auth-core/server/deferredAuthService";
|
|
7
|
+
import { defineProvider } from "@jskit-ai/kernel/shared/capabilities";
|
|
8
|
+
import { randomBytes } from "node:crypto";
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { createLocalFileBackend } from "../lib/fileBackend.js";
|
|
12
|
+
import { createLocalAuthService } from "../lib/service.js";
|
|
13
|
+
|
|
14
|
+
const DEFAULT_STORE_DIR = ".jskit/auth";
|
|
15
|
+
function resolveStoreDir(env, appRoot) {
|
|
16
|
+
const configured = String(env.AUTH_LOCAL_STORE_DIR || DEFAULT_STORE_DIR).trim() || DEFAULT_STORE_DIR;
|
|
17
|
+
return path.resolve(appRoot, configured);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function resolveSessionSecret(env, { storeDir, isProduction }) {
|
|
21
|
+
const explicit = String(env.AUTH_LOCAL_SESSION_SECRET || "").trim();
|
|
22
|
+
if (explicit) return explicit;
|
|
23
|
+
if (isProduction) throw new Error("AUTH_LOCAL_SESSION_SECRET is required for local auth in production.");
|
|
24
|
+
|
|
25
|
+
const secretPath = path.join(storeDir, "session.secret");
|
|
26
|
+
try {
|
|
27
|
+
const existing = fs.readFileSync(secretPath, "utf8").trim();
|
|
28
|
+
if (existing) return existing;
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (!error || error.code !== "ENOENT") throw error;
|
|
31
|
+
}
|
|
32
|
+
fs.mkdirSync(storeDir, { recursive: true, mode: 0o700 });
|
|
33
|
+
const generated = randomBytes(32).toString("base64url");
|
|
34
|
+
fs.writeFileSync(secretPath, `${generated}\n`, { mode: 0o600 });
|
|
35
|
+
return generated;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function resolveSmtpConfig(env) {
|
|
39
|
+
const host = String(env.AUTH_LOCAL_SMTP_HOST || "").trim();
|
|
40
|
+
if (!host) return { smtpConfigured: false, smtp: null };
|
|
41
|
+
const from = String(env.AUTH_LOCAL_SMTP_FROM || "").trim();
|
|
42
|
+
if (!from) throw new Error("AUTH_LOCAL_SMTP_FROM is required when AUTH_LOCAL_SMTP_HOST is set.");
|
|
43
|
+
return {
|
|
44
|
+
smtpConfigured: true,
|
|
45
|
+
smtp: {
|
|
46
|
+
host,
|
|
47
|
+
port: Number(env.AUTH_LOCAL_SMTP_PORT || 587),
|
|
48
|
+
secure: parseBooleanFlag(env.AUTH_LOCAL_SMTP_SECURE, false),
|
|
49
|
+
user: String(env.AUTH_LOCAL_SMTP_USER || "").trim(),
|
|
50
|
+
password: String(env.AUTH_LOCAL_SMTP_PASSWORD || ""),
|
|
51
|
+
from,
|
|
52
|
+
replyTo: String(env.AUTH_LOCAL_SMTP_REPLY_TO || "").trim()
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resolveAppPublicUrl(env, { smtpConfigured }) {
|
|
58
|
+
const configured = String(env.APP_PUBLIC_URL || "").trim();
|
|
59
|
+
if (!configured) {
|
|
60
|
+
if (smtpConfigured) throw new Error("APP_PUBLIC_URL is required when local auth SMTP recovery is configured.");
|
|
61
|
+
return "http://localhost:5173";
|
|
62
|
+
}
|
|
63
|
+
let parsed;
|
|
64
|
+
try {
|
|
65
|
+
parsed = new URL(configured);
|
|
66
|
+
} catch {
|
|
67
|
+
throw new Error("APP_PUBLIC_URL must be a valid URL when local auth SMTP recovery is configured.");
|
|
68
|
+
}
|
|
69
|
+
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
70
|
+
throw new Error("APP_PUBLIC_URL must use http or https when local auth SMTP recovery is configured.");
|
|
71
|
+
}
|
|
72
|
+
return parsed.toString().replace(/\/$/u, "");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function resolveConfig({ appRoot, backend, env, logger }) {
|
|
76
|
+
const nodeEnv = String(env.NODE_ENV || "development").trim() || "development";
|
|
77
|
+
const isProduction = nodeEnv === "production";
|
|
78
|
+
const storeDir = resolveStoreDir(env, appRoot);
|
|
79
|
+
if (!backend && isProduction && !parseBooleanFlag(env.AUTH_LOCAL_FILE_PRODUCTION_ACK, false)) {
|
|
80
|
+
throw new Error("AUTH_LOCAL_FILE_PRODUCTION_ACK is required to use the local file auth backend in production.");
|
|
81
|
+
}
|
|
82
|
+
const smtp = resolveSmtpConfig(env);
|
|
83
|
+
const devAuth = resolveDevAuthPolicyFromEnv(env);
|
|
84
|
+
assertDevAuthPolicy(devAuth);
|
|
85
|
+
return {
|
|
86
|
+
storeDir,
|
|
87
|
+
nodeEnv,
|
|
88
|
+
logger,
|
|
89
|
+
appPublicUrl: resolveAppPublicUrl(env, smtp),
|
|
90
|
+
sessionSecret: resolveSessionSecret(env, { storeDir, isProduction }),
|
|
91
|
+
recoveryDevOutput: String(env.AUTH_LOCAL_RECOVERY_DEV_OUTPUT || "log").trim().toLowerCase() || "log",
|
|
92
|
+
devAuth,
|
|
93
|
+
...smtp
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const AuthLocalProvider = defineProvider({
|
|
98
|
+
id: "auth.local",
|
|
99
|
+
requires: {
|
|
100
|
+
appRoot: "runtime.app-root",
|
|
101
|
+
env: "runtime.env",
|
|
102
|
+
extensions: "auth.extensions",
|
|
103
|
+
logger: "runtime.logger"
|
|
104
|
+
},
|
|
105
|
+
optional: {
|
|
106
|
+
backend: "auth.local.backend",
|
|
107
|
+
passwordStrategy: "auth.local.password-strategy"
|
|
108
|
+
},
|
|
109
|
+
provides: {
|
|
110
|
+
authService: "auth.service"
|
|
111
|
+
},
|
|
112
|
+
setup() {
|
|
113
|
+
return { authService: createDeferredAuthService() };
|
|
114
|
+
},
|
|
115
|
+
boot(dependencies, { outputs }) {
|
|
116
|
+
const { appRoot, backend, env, extensions, logger, passwordStrategy } = dependencies;
|
|
117
|
+
const config = resolveConfig({ appRoot, backend, env, logger });
|
|
118
|
+
const baseService = createLocalAuthService({
|
|
119
|
+
backend: backend || createLocalFileBackend({ storeDir: config.storeDir }),
|
|
120
|
+
config,
|
|
121
|
+
profileProjector: extensions.resolveProfileProjector(),
|
|
122
|
+
passwordStrategy,
|
|
123
|
+
invitationContextResolver: extensions.resolveInvitationContextResolver()
|
|
124
|
+
});
|
|
125
|
+
attachDeferredAuthService(outputs.authService, extensions.decorateService(baseService));
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
export { AuthLocalProvider, resolveConfig };
|
|
@@ -1,25 +1,14 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
2
|
import test from "node:test";
|
|
4
3
|
import packageJson from "../package.json" with { type: "json" };
|
|
5
4
|
|
|
6
5
|
const packageMetadata = packageJson.jskit;
|
|
7
6
|
|
|
8
|
-
test("local auth
|
|
9
|
-
assert.
|
|
10
|
-
|
|
11
|
-
AUTH_PROVIDER: "local"
|
|
12
|
-
},
|
|
13
|
-
services: [],
|
|
14
|
-
steps: []
|
|
15
|
-
});
|
|
7
|
+
test("local auth has no package mutation or CI recipe", () => {
|
|
8
|
+
assert.equal(Object.hasOwn(packageMetadata, "ci"), false);
|
|
9
|
+
assert.equal(Object.hasOwn(packageMetadata, "mutations"), false);
|
|
16
10
|
});
|
|
17
11
|
|
|
18
|
-
test("local auth
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
assert.equal(
|
|
22
|
-
packageMetadata.mutations.dependencies.runtime.nodemailer,
|
|
23
|
-
packageJson.dependencies.nodemailer
|
|
24
|
-
);
|
|
12
|
+
test("local auth declares Nodemailer as its own runtime dependency", () => {
|
|
13
|
+
assert.equal(typeof packageJson.dependencies.nodemailer, "string");
|
|
25
14
|
});
|
|
@@ -3,13 +3,12 @@ import fs from "node:fs/promises";
|
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import test from "node:test";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { createActionProvider } from "@jskit-ai/kernel/server/actions";
|
|
7
|
+
import { createCapabilityRuntime, defineProvider } from "@jskit-ai/kernel/shared/capabilities";
|
|
8
|
+
import { createAuthExtensions } from "@jskit-ai/auth-core/server/authExtensions";
|
|
9
9
|
import { DEV_AUTH_SECRET_HEADER } from "@jskit-ai/auth-core/server/devAuth";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { AuthProviderServiceProvider } from "../src/server/providers/AuthProviderServiceProvider.js";
|
|
10
|
+
import { AuthFeature } from "@jskit-ai/auth-core/server/providers/AuthFeature";
|
|
11
|
+
import { AuthLocalProvider } from "../src/server/providers/AuthLocalProvider.js";
|
|
13
12
|
import {
|
|
14
13
|
createLocalAuthRegisterHookDecorator,
|
|
15
14
|
createLocalAuthService,
|
|
@@ -21,23 +20,6 @@ import {
|
|
|
21
20
|
|
|
22
21
|
const DEV_AUTH_SECRET = "local-preview-exchange-secret";
|
|
23
22
|
|
|
24
|
-
function createAppConfigFixture() {
|
|
25
|
-
return {
|
|
26
|
-
surfaceModeAll: "all",
|
|
27
|
-
surfaceDefaultId: "home",
|
|
28
|
-
surfaceDefinitions: {
|
|
29
|
-
home: { id: "home", pagesRoot: "", enabled: true, requiresAuth: false, requiresWorkspace: false },
|
|
30
|
-
console: {
|
|
31
|
-
id: "console",
|
|
32
|
-
pagesRoot: "console",
|
|
33
|
-
enabled: true,
|
|
34
|
-
requiresAuth: true,
|
|
35
|
-
requiresWorkspace: false
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
23
|
function createReplyFixture() {
|
|
42
24
|
const cookies = {};
|
|
43
25
|
const cookieOptions = {};
|
|
@@ -94,67 +76,82 @@ function devAuthEnv() {
|
|
|
94
76
|
async function createStartedApp({
|
|
95
77
|
env = {},
|
|
96
78
|
profileProjector = null,
|
|
97
|
-
profileProjectorFactory = null,
|
|
98
79
|
passwordStrategy = null,
|
|
99
80
|
invitationContextResolver = null,
|
|
100
81
|
logger = null,
|
|
101
|
-
|
|
82
|
+
configureExtensions = null
|
|
102
83
|
} = {}) {
|
|
103
84
|
const storeDir = await fs.mkdtemp(path.join(os.tmpdir(), "jskit-auth-local-"));
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
85
|
+
const backend = createLocalFileBackend({ storeDir });
|
|
86
|
+
const extensions = createAuthExtensions();
|
|
87
|
+
const runtimeLogger = logger || {
|
|
88
|
+
info() {},
|
|
89
|
+
warn() {},
|
|
90
|
+
error() {},
|
|
91
|
+
debug() {}
|
|
92
|
+
};
|
|
93
|
+
if (typeof configureExtensions === "function") configureExtensions(extensions, runtimeLogger);
|
|
94
|
+
if (profileProjector) {
|
|
95
|
+
extensions.registerProfileProjector({
|
|
96
|
+
projectorId: "test.profile",
|
|
97
|
+
...profileProjector
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (invitationContextResolver) {
|
|
101
|
+
extensions.registerInvitationContextResolver({
|
|
102
|
+
resolverId: "test.invitation",
|
|
103
|
+
...invitationContextResolver
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
let authService = null;
|
|
107
|
+
let actionExecutor = null;
|
|
108
|
+
const probe = defineProvider({
|
|
109
|
+
id: "test.auth.probe",
|
|
110
|
+
requires: {
|
|
111
|
+
actions: "runtime.actions",
|
|
112
|
+
service: "auth.service"
|
|
113
|
+
},
|
|
114
|
+
setup({ actions, service }) {
|
|
115
|
+
actionExecutor = actions;
|
|
116
|
+
authService = service;
|
|
117
|
+
return {};
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
const inputs = {
|
|
121
|
+
"runtime.app-root": storeDir,
|
|
122
|
+
"runtime.env": {
|
|
108
123
|
AUTH_LOCAL_STORE_DIR: storeDir,
|
|
109
124
|
AUTH_LOCAL_RECOVERY_DEV_OUTPUT: "response",
|
|
110
125
|
APP_PUBLIC_URL: "http://localhost:5173",
|
|
111
126
|
NODE_ENV: "test",
|
|
112
127
|
...env
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
"
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
);
|
|
123
|
-
app.instance("domainEvents", {
|
|
124
|
-
async publish() {}
|
|
125
|
-
});
|
|
126
|
-
if (profileProjector) {
|
|
127
|
-
app.instance("auth.profile.projector", profileProjector);
|
|
128
|
-
}
|
|
129
|
-
if (profileProjectorFactory) {
|
|
130
|
-
app.singleton("auth.profile.projector", profileProjectorFactory);
|
|
131
|
-
}
|
|
132
|
-
if (passwordStrategy) {
|
|
133
|
-
app.instance("auth.local.passwordStrategy", passwordStrategy);
|
|
134
|
-
}
|
|
135
|
-
if (invitationContextResolver) {
|
|
136
|
-
app.instance("auth.invitationContextResolver", invitationContextResolver);
|
|
137
|
-
}
|
|
138
|
-
if (typeof configureApp === "function") {
|
|
139
|
-
configureApp(app);
|
|
140
|
-
}
|
|
141
|
-
await app.start({
|
|
128
|
+
},
|
|
129
|
+
"runtime.logger": runtimeLogger,
|
|
130
|
+
"auth.extensions": extensions,
|
|
131
|
+
"auth.local.backend": backend,
|
|
132
|
+
...(passwordStrategy ? { "auth.local.password-strategy": passwordStrategy } : {}),
|
|
133
|
+
};
|
|
134
|
+
const runtime = createCapabilityRuntime({
|
|
135
|
+
inputs,
|
|
142
136
|
providers: [
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
137
|
+
createActionProvider(),
|
|
138
|
+
AuthLocalProvider,
|
|
139
|
+
AuthFeature,
|
|
140
|
+
probe
|
|
147
141
|
]
|
|
148
142
|
});
|
|
143
|
+
await runtime.start();
|
|
149
144
|
return {
|
|
150
|
-
|
|
145
|
+
actionExecutor,
|
|
146
|
+
authService,
|
|
147
|
+
backend,
|
|
148
|
+
runtime,
|
|
151
149
|
storeDir
|
|
152
150
|
};
|
|
153
151
|
}
|
|
154
152
|
|
|
155
153
|
test("local auth provider registers, logs in, reads session, and logs out with file backend", async () => {
|
|
156
|
-
const {
|
|
157
|
-
const authService = app.make("authService");
|
|
154
|
+
const { actionExecutor, authService, storeDir } = await createStartedApp();
|
|
158
155
|
const capabilities = authService.getCapabilities();
|
|
159
156
|
assert.equal(capabilities.provider.id, "local");
|
|
160
157
|
assert.equal(capabilities.features.password.login, true);
|
|
@@ -178,7 +175,6 @@ test("local auth provider registers, logs in, reads session, and logs out with f
|
|
|
178
175
|
assert.equal(session.authenticated, true);
|
|
179
176
|
assert.equal(session.actor.email, "ada@example.com");
|
|
180
177
|
|
|
181
|
-
const actionExecutor = app.make("actionExecutor");
|
|
182
178
|
const definitions = actionExecutor.listDefinitions();
|
|
183
179
|
assert.equal(definitions.some((definition) => definition.id === "auth.login.password"), true);
|
|
184
180
|
const actionSession = await actionExecutor.execute({
|
|
@@ -204,10 +200,9 @@ test("local auth provider registers, logs in, reads session, and logs out with f
|
|
|
204
200
|
});
|
|
205
201
|
|
|
206
202
|
test("local file auth login-as issues native cookies for an existing user", async () => {
|
|
207
|
-
const {
|
|
203
|
+
const { authService } = await createStartedApp({
|
|
208
204
|
env: devAuthEnv()
|
|
209
205
|
});
|
|
210
|
-
const authService = app.make("authService");
|
|
211
206
|
await authService.register({
|
|
212
207
|
displayName: "Ada",
|
|
213
208
|
email: "ada@example.com",
|
|
@@ -244,7 +239,7 @@ test("local login-as never invokes the mutating profile projector", async () =>
|
|
|
244
239
|
};
|
|
245
240
|
let findCalls = 0;
|
|
246
241
|
let syncCalls = 0;
|
|
247
|
-
const {
|
|
242
|
+
const { authService } = await createStartedApp({
|
|
248
243
|
env: devAuthEnv(),
|
|
249
244
|
profileProjector: {
|
|
250
245
|
async findByIdentity() {
|
|
@@ -260,7 +255,6 @@ test("local login-as never invokes the mutating profile projector", async () =>
|
|
|
260
255
|
}
|
|
261
256
|
}
|
|
262
257
|
});
|
|
263
|
-
const authService = app.make("authService");
|
|
264
258
|
const registered = await authService.register({
|
|
265
259
|
displayName: "Ada",
|
|
266
260
|
email: "ada@example.com",
|
|
@@ -286,7 +280,7 @@ test("local login-as never invokes the mutating profile projector", async () =>
|
|
|
286
280
|
});
|
|
287
281
|
|
|
288
282
|
test("local login-as rejects missing, disabled, and unprojected users", async () => {
|
|
289
|
-
const {
|
|
283
|
+
const { authService, backend } = await createStartedApp({
|
|
290
284
|
env: devAuthEnv(),
|
|
291
285
|
profileProjector: {
|
|
292
286
|
async findByIdentity() {
|
|
@@ -297,7 +291,6 @@ test("local login-as rejects missing, disabled, and unprojected users", async ()
|
|
|
297
291
|
}
|
|
298
292
|
}
|
|
299
293
|
});
|
|
300
|
-
const backend = app.make("auth.local.backend");
|
|
301
294
|
const password = await hashPassword("stored password value");
|
|
302
295
|
await backend.withTransaction(async (tx) => {
|
|
303
296
|
await tx.users.create({
|
|
@@ -315,7 +308,6 @@ test("local login-as rejects missing, disabled, and unprojected users", async ()
|
|
|
315
308
|
password
|
|
316
309
|
});
|
|
317
310
|
});
|
|
318
|
-
const authService = app.make("authService");
|
|
319
311
|
|
|
320
312
|
for (const email of [
|
|
321
313
|
"missing@example.com",
|
|
@@ -330,10 +322,9 @@ test("local login-as rejects missing, disabled, and unprojected users", async ()
|
|
|
330
322
|
});
|
|
331
323
|
|
|
332
324
|
test("local login-as rejects missing, incorrect, and non-loopback exchange authorization", async () => {
|
|
333
|
-
const {
|
|
325
|
+
const { authService } = await createStartedApp({
|
|
334
326
|
env: devAuthEnv()
|
|
335
327
|
});
|
|
336
|
-
const authService = app.make("authService");
|
|
337
328
|
await authService.register({
|
|
338
329
|
displayName: "Ada",
|
|
339
330
|
email: "ada@example.com",
|
|
@@ -379,7 +370,7 @@ test("local auth provider rejects dev login-as enablement in production", async
|
|
|
379
370
|
test("local auth provider applies auth service decorators for blocking and non-blocking hooks", async () => {
|
|
380
371
|
const calls = [];
|
|
381
372
|
const errors = [];
|
|
382
|
-
const {
|
|
373
|
+
const { authService } = await createStartedApp({
|
|
383
374
|
logger: {
|
|
384
375
|
info() {},
|
|
385
376
|
warn(payload, message) {
|
|
@@ -388,12 +379,11 @@ test("local auth provider applies auth service decorators for blocking and non-b
|
|
|
388
379
|
error() {},
|
|
389
380
|
debug() {}
|
|
390
381
|
},
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
...createLocalAuthRegisterHookDecorator({
|
|
382
|
+
configureExtensions(extensions, runtimeLogger) {
|
|
383
|
+
extensions.registerServiceDecorator(createLocalAuthRegisterHookDecorator({
|
|
394
384
|
decoratorId: "test.auth.local.nonBlockingHook",
|
|
395
385
|
order: 20,
|
|
396
|
-
logger:
|
|
386
|
+
logger: runtimeLogger,
|
|
397
387
|
hook: {
|
|
398
388
|
hookId: "audit",
|
|
399
389
|
blocking: false,
|
|
@@ -402,10 +392,8 @@ test("local auth provider applies auth service decorators for blocking and non-b
|
|
|
402
392
|
throw new Error("audit unavailable");
|
|
403
393
|
}
|
|
404
394
|
}
|
|
405
|
-
})
|
|
406
|
-
|
|
407
|
-
registerAuthServiceDecorator(app, "test.auth.local.blockingHook", () =>
|
|
408
|
-
createLocalAuthRegisterHookDecorator({
|
|
395
|
+
}));
|
|
396
|
+
extensions.registerServiceDecorator(createLocalAuthRegisterHookDecorator({
|
|
409
397
|
decoratorId: "test.auth.local.blockingHook",
|
|
410
398
|
order: 10,
|
|
411
399
|
hook: {
|
|
@@ -418,11 +406,9 @@ test("local auth provider applies auth service decorators for blocking and non-b
|
|
|
418
406
|
}
|
|
419
407
|
}
|
|
420
408
|
}
|
|
421
|
-
})
|
|
422
|
-
);
|
|
409
|
+
}));
|
|
423
410
|
}
|
|
424
411
|
});
|
|
425
|
-
const authService = app.make("authService");
|
|
426
412
|
|
|
427
413
|
const registered = await authService.register({
|
|
428
414
|
email: "hooks@example.com",
|
|
@@ -472,7 +458,7 @@ test("local auth register hook decorators require explicit blocking mode", () =>
|
|
|
472
458
|
);
|
|
473
459
|
});
|
|
474
460
|
|
|
475
|
-
test("local auth provider
|
|
461
|
+
test("local auth provider accepts a custom password strategy capability", async () => {
|
|
476
462
|
const passwordStrategy = {
|
|
477
463
|
prefix: "strategy",
|
|
478
464
|
async hashPassword(password) {
|
|
@@ -487,8 +473,7 @@ test("local auth provider resolves a custom password strategy from the container
|
|
|
487
473
|
return record?.algorithm === "test-password" && record?.hash === `${this.prefix}-${password}`;
|
|
488
474
|
}
|
|
489
475
|
};
|
|
490
|
-
const {
|
|
491
|
-
const authService = app.make("authService");
|
|
476
|
+
const { authService } = await createStartedApp({ passwordStrategy });
|
|
492
477
|
|
|
493
478
|
await authService.register({
|
|
494
479
|
email: "strategy@example.com",
|
|
@@ -751,8 +736,7 @@ test("local auth password strategy handles register, reset, login, and change pa
|
|
|
751
736
|
);
|
|
752
737
|
}
|
|
753
738
|
};
|
|
754
|
-
const {
|
|
755
|
-
const authService = app.make("authService");
|
|
739
|
+
const { authService } = await createStartedApp({ passwordStrategy });
|
|
756
740
|
|
|
757
741
|
await authService.register({
|
|
758
742
|
email: "strategy-paths@example.com",
|
|
@@ -817,8 +801,7 @@ test("local auth password strategy handles register, reset, login, and change pa
|
|
|
817
801
|
});
|
|
818
802
|
|
|
819
803
|
test("local auth provider completes recovery through a recovery-scoped session", async () => {
|
|
820
|
-
const {
|
|
821
|
-
const authService = app.make("authService");
|
|
804
|
+
const { authService } = await createStartedApp();
|
|
822
805
|
await authService.register({
|
|
823
806
|
email: "Recovery@example.com",
|
|
824
807
|
password: "correct horse battery staple",
|
|
@@ -890,8 +873,7 @@ test("local auth provider completes recovery through a recovery-scoped session",
|
|
|
890
873
|
});
|
|
891
874
|
|
|
892
875
|
test("local auth reset invalidates other outstanding recovery tokens for the user", async () => {
|
|
893
|
-
const {
|
|
894
|
-
const authService = app.make("authService");
|
|
876
|
+
const { authService } = await createStartedApp();
|
|
895
877
|
await authService.register({
|
|
896
878
|
email: "recovery-tokens@example.com",
|
|
897
879
|
password: "correct horse battery staple",
|
|
@@ -933,8 +915,7 @@ test("local auth reset invalidates other outstanding recovery tokens for the use
|
|
|
933
915
|
});
|
|
934
916
|
|
|
935
917
|
test("local auth provider changes password through the account-security contract", async () => {
|
|
936
|
-
const {
|
|
937
|
-
const authService = app.make("authService");
|
|
918
|
+
const { authService } = await createStartedApp();
|
|
938
919
|
const registered = await authService.register({
|
|
939
920
|
email: "Security@example.com",
|
|
940
921
|
password: "correct horse battery staple",
|
|
@@ -982,36 +963,9 @@ test("local auth provider changes password through the account-security contract
|
|
|
982
963
|
assert.equal(login.actor.email, "security@example.com");
|
|
983
964
|
});
|
|
984
965
|
|
|
985
|
-
test("local auth provider defers profile projector resolution until projection is needed", async () => {
|
|
986
|
-
let projectorFactoryCalls = 0;
|
|
987
|
-
const { app } = await createStartedApp({
|
|
988
|
-
profileProjectorFactory() {
|
|
989
|
-
projectorFactoryCalls += 1;
|
|
990
|
-
return {
|
|
991
|
-
async syncIdentityProfile(profile) {
|
|
992
|
-
return {
|
|
993
|
-
...profile,
|
|
994
|
-
id: "projected-user",
|
|
995
|
-
profileSource: "users"
|
|
996
|
-
};
|
|
997
|
-
}
|
|
998
|
-
};
|
|
999
|
-
}
|
|
1000
|
-
});
|
|
1001
|
-
assert.equal(projectorFactoryCalls, 0);
|
|
1002
|
-
const authService = app.make("authService");
|
|
1003
|
-
assert.equal(projectorFactoryCalls, 0);
|
|
1004
|
-
const registered = await authService.register({
|
|
1005
|
-
email: "projected@example.com",
|
|
1006
|
-
password: "correct horse battery staple",
|
|
1007
|
-
displayName: "Projected User"
|
|
1008
|
-
});
|
|
1009
|
-
assert.equal(projectorFactoryCalls, 1);
|
|
1010
|
-
assert.equal(registered.actor.appUserId, "projected-user");
|
|
1011
|
-
});
|
|
1012
966
|
test("local auth provider projects app profile when auth.profile.projector is installed", async () => {
|
|
1013
967
|
const projectedProfiles = [];
|
|
1014
|
-
const {
|
|
968
|
+
const { authService } = await createStartedApp({
|
|
1015
969
|
profileProjector: {
|
|
1016
970
|
async syncIdentityProfile(profile) {
|
|
1017
971
|
projectedProfiles.push(profile);
|
|
@@ -1023,7 +977,6 @@ test("local auth provider projects app profile when auth.profile.projector is in
|
|
|
1023
977
|
}
|
|
1024
978
|
}
|
|
1025
979
|
});
|
|
1026
|
-
const authService = app.make("authService");
|
|
1027
980
|
assert.equal(authService.getCapabilities().features.appProfileProjection, true);
|
|
1028
981
|
|
|
1029
982
|
const registered = await authService.register({
|
|
@@ -1041,7 +994,7 @@ test("local auth provider projects app profile when auth.profile.projector is in
|
|
|
1041
994
|
test("local auth provider passes resolved invitation context into profile projection during registration", async () => {
|
|
1042
995
|
const resolverCalls = [];
|
|
1043
996
|
const projectionCalls = [];
|
|
1044
|
-
const {
|
|
997
|
+
const { authService } = await createStartedApp({
|
|
1045
998
|
invitationContextResolver: {
|
|
1046
999
|
async resolveInvitationContext(invitation) {
|
|
1047
1000
|
resolverCalls.push(invitation);
|
|
@@ -1067,7 +1020,6 @@ test("local auth provider passes resolved invitation context into profile projec
|
|
|
1067
1020
|
}
|
|
1068
1021
|
}
|
|
1069
1022
|
});
|
|
1070
|
-
const authService = app.make("authService");
|
|
1071
1023
|
|
|
1072
1024
|
const registered = await authService.register({
|
|
1073
1025
|
email: "Invitee@Example.com",
|
|
@@ -1103,44 +1055,16 @@ test("local auth provider passes resolved invitation context into profile projec
|
|
|
1103
1055
|
assert.equal(registered.actor.appUserId, "invited-app-user");
|
|
1104
1056
|
});
|
|
1105
1057
|
|
|
1106
|
-
test("local auth provider rejects AUTH_PROVIDER mismatches", async () => {
|
|
1107
|
-
const app = createApplication();
|
|
1108
|
-
app.instance("appConfig", createAppConfigFixture());
|
|
1109
|
-
app.instance("jskit.env", {
|
|
1110
|
-
AUTH_PROVIDER: "supabase",
|
|
1111
|
-
NODE_ENV: "test"
|
|
1112
|
-
});
|
|
1113
|
-
|
|
1114
|
-
await assert.rejects(
|
|
1115
|
-
() =>
|
|
1116
|
-
app.start({
|
|
1117
|
-
providers: [ActionRuntimeServiceProvider, AuthLocalServiceProvider]
|
|
1118
|
-
}),
|
|
1119
|
-
(error) => /AUTH_PROVIDER is "supabase"/.test(String(error.details?.cause?.message || error.message || ""))
|
|
1120
|
-
);
|
|
1121
|
-
});
|
|
1122
|
-
|
|
1123
1058
|
test("local auth provider requires an explicit public URL for SMTP recovery", async () => {
|
|
1124
|
-
const storeDir = await fs.mkdtemp(path.join(os.tmpdir(), "jskit-auth-local-"));
|
|
1125
|
-
const app = createApplication();
|
|
1126
|
-
app.instance("appConfig", createAppConfigFixture());
|
|
1127
|
-
app.instance("jskit.env", {
|
|
1128
|
-
AUTH_PROVIDER: "local",
|
|
1129
|
-
AUTH_LOCAL_STORE_DIR: storeDir,
|
|
1130
|
-
AUTH_LOCAL_SMTP_HOST: "smtp.example.com",
|
|
1131
|
-
AUTH_LOCAL_SMTP_FROM: "support@example.com",
|
|
1132
|
-
NODE_ENV: "test"
|
|
1133
|
-
});
|
|
1134
|
-
|
|
1135
1059
|
await assert.rejects(
|
|
1136
|
-
() =>
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1060
|
+
() => createStartedApp({
|
|
1061
|
+
env: {
|
|
1062
|
+
APP_PUBLIC_URL: "",
|
|
1063
|
+
AUTH_LOCAL_SMTP_HOST: "smtp.example.com",
|
|
1064
|
+
AUTH_LOCAL_SMTP_FROM: "support@example.com"
|
|
1065
|
+
}
|
|
1066
|
+
}),
|
|
1067
|
+
/APP_PUBLIC_URL is required when local auth SMTP recovery is configured/
|
|
1144
1068
|
);
|
|
1145
1069
|
});
|
|
1146
1070
|
|
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
import { applyAuthServiceDecorators } from "@jskit-ai/auth-core/server/authServiceDecoratorRegistry";
|
|
2
|
-
import { parseBooleanFlag } from "@jskit-ai/auth-core/server/booleanFlag";
|
|
3
|
-
import {
|
|
4
|
-
assertDevAuthPolicy,
|
|
5
|
-
resolveDevAuthPolicyFromEnv
|
|
6
|
-
} from "@jskit-ai/auth-core/server/devAuth";
|
|
7
|
-
import fs from "node:fs";
|
|
8
|
-
import path from "node:path";
|
|
9
|
-
import { randomBytes } from "node:crypto";
|
|
10
|
-
import { createLocalAuthService } from "../lib/service.js";
|
|
11
|
-
import { createLocalFileBackend } from "../lib/fileBackend.js";
|
|
12
|
-
|
|
13
|
-
const DEFAULT_STORE_DIR = ".jskit/auth";
|
|
14
|
-
|
|
15
|
-
function resolveLocalBackendMode(scope) {
|
|
16
|
-
const env = resolveRuntimeEnv(scope);
|
|
17
|
-
return String(env.AUTH_LOCAL_BACKEND || "file").trim().toLowerCase() || "file";
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function resolveRuntimeEnv(scope) {
|
|
21
|
-
const env = scope && typeof scope.has === "function" && scope.has("jskit.env") ? scope.make("jskit.env") : {};
|
|
22
|
-
return {
|
|
23
|
-
...process.env,
|
|
24
|
-
...(env && typeof env === "object" ? env : {})
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function assertSelectedAuthProvider(env) {
|
|
29
|
-
const selectedProvider = String(env?.AUTH_PROVIDER || "").trim().toLowerCase();
|
|
30
|
-
if (selectedProvider && selectedProvider !== "local") {
|
|
31
|
-
throw new Error(
|
|
32
|
-
`AUTH_PROVIDER is "${selectedProvider}", but @jskit-ai/auth-provider-local-core is installed as the selected auth provider.`
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function resolveStoreDir(env) {
|
|
38
|
-
return path.resolve(String(env.AUTH_LOCAL_STORE_DIR || DEFAULT_STORE_DIR).trim() || DEFAULT_STORE_DIR);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function resolveSessionSecret(env, { storeDir, isProduction }) {
|
|
42
|
-
const explicit = String(env.AUTH_LOCAL_SESSION_SECRET || "").trim();
|
|
43
|
-
if (explicit) {
|
|
44
|
-
return explicit;
|
|
45
|
-
}
|
|
46
|
-
if (isProduction) {
|
|
47
|
-
throw new Error("AUTH_LOCAL_SESSION_SECRET is required for local auth in production.");
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const secretPath = path.join(storeDir, "session.secret");
|
|
51
|
-
try {
|
|
52
|
-
const existing = fs.readFileSync(secretPath, "utf8").trim();
|
|
53
|
-
if (existing) {
|
|
54
|
-
return existing;
|
|
55
|
-
}
|
|
56
|
-
} catch (error) {
|
|
57
|
-
if (!error || error.code !== "ENOENT") {
|
|
58
|
-
throw error;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
fs.mkdirSync(storeDir, { recursive: true, mode: 0o700 });
|
|
63
|
-
const generated = randomBytes(32).toString("base64url");
|
|
64
|
-
fs.writeFileSync(secretPath, `${generated}\n`, { mode: 0o600 });
|
|
65
|
-
return generated;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function resolveSmtpConfig(env) {
|
|
69
|
-
const host = String(env.AUTH_LOCAL_SMTP_HOST || "").trim();
|
|
70
|
-
if (!host) {
|
|
71
|
-
return {
|
|
72
|
-
smtpConfigured: false,
|
|
73
|
-
smtp: null
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
const from = String(env.AUTH_LOCAL_SMTP_FROM || "").trim();
|
|
77
|
-
if (!from) {
|
|
78
|
-
throw new Error("AUTH_LOCAL_SMTP_FROM is required when AUTH_LOCAL_SMTP_HOST is set.");
|
|
79
|
-
}
|
|
80
|
-
return {
|
|
81
|
-
smtpConfigured: true,
|
|
82
|
-
smtp: {
|
|
83
|
-
host,
|
|
84
|
-
port: Number(env.AUTH_LOCAL_SMTP_PORT || 587),
|
|
85
|
-
secure: parseBooleanFlag(env.AUTH_LOCAL_SMTP_SECURE, false),
|
|
86
|
-
user: String(env.AUTH_LOCAL_SMTP_USER || "").trim(),
|
|
87
|
-
password: String(env.AUTH_LOCAL_SMTP_PASSWORD || ""),
|
|
88
|
-
from,
|
|
89
|
-
replyTo: String(env.AUTH_LOCAL_SMTP_REPLY_TO || "").trim()
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function resolveAppPublicUrl(env, { smtpConfigured }) {
|
|
95
|
-
const configured = String(env.APP_PUBLIC_URL || "").trim();
|
|
96
|
-
if (!configured) {
|
|
97
|
-
if (smtpConfigured) {
|
|
98
|
-
throw new Error("APP_PUBLIC_URL is required when local auth SMTP recovery is configured.");
|
|
99
|
-
}
|
|
100
|
-
return "http://localhost:5173";
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
let parsed;
|
|
104
|
-
try {
|
|
105
|
-
parsed = new URL(configured);
|
|
106
|
-
} catch {
|
|
107
|
-
throw new Error("APP_PUBLIC_URL must be a valid URL when local auth SMTP recovery is configured.");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
111
|
-
throw new Error("APP_PUBLIC_URL must use http or https when local auth SMTP recovery is configured.");
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return parsed.toString().replace(/\/$/, "");
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function resolveConfig(scope) {
|
|
118
|
-
const env = resolveRuntimeEnv(scope);
|
|
119
|
-
assertSelectedAuthProvider(env);
|
|
120
|
-
const nodeEnv = String(env.NODE_ENV || "development").trim() || "development";
|
|
121
|
-
const isProduction = nodeEnv === "production";
|
|
122
|
-
const backend = String(env.AUTH_LOCAL_BACKEND || "file").trim().toLowerCase() || "file";
|
|
123
|
-
const storeDir = resolveStoreDir(env);
|
|
124
|
-
if (backend === "file" && isProduction && !parseBooleanFlag(env.AUTH_LOCAL_FILE_PRODUCTION_ACK, false)) {
|
|
125
|
-
throw new Error("AUTH_LOCAL_FILE_PRODUCTION_ACK is required to use the local file auth backend in production.");
|
|
126
|
-
}
|
|
127
|
-
const smtp = resolveSmtpConfig(env);
|
|
128
|
-
const devAuth = resolveDevAuthPolicyFromEnv(env);
|
|
129
|
-
assertDevAuthPolicy(devAuth);
|
|
130
|
-
return {
|
|
131
|
-
backend,
|
|
132
|
-
storeDir,
|
|
133
|
-
nodeEnv,
|
|
134
|
-
logger: scope.has("jskit.logger") ? scope.make("jskit.logger") : console,
|
|
135
|
-
appPublicUrl: resolveAppPublicUrl(env, smtp),
|
|
136
|
-
sessionSecret: resolveSessionSecret(env, {
|
|
137
|
-
storeDir,
|
|
138
|
-
isProduction
|
|
139
|
-
}),
|
|
140
|
-
recoveryDevOutput: String(env.AUTH_LOCAL_RECOVERY_DEV_OUTPUT || "log").trim().toLowerCase() || "log",
|
|
141
|
-
devAuth,
|
|
142
|
-
...smtp
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function createLazyProfileProjector(scope) {
|
|
147
|
-
if (!scope.has("auth.profile.projector")) {
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
const resolveMethod = (methodName) => {
|
|
151
|
-
const projector = scope.make("auth.profile.projector");
|
|
152
|
-
if (!projector || typeof projector[methodName] !== "function") {
|
|
153
|
-
throw new Error(`auth.profile.projector.${methodName}() must be a function.`);
|
|
154
|
-
}
|
|
155
|
-
return projector[methodName].bind(projector);
|
|
156
|
-
};
|
|
157
|
-
return {
|
|
158
|
-
findByIdentity(identity, options = {}) {
|
|
159
|
-
return resolveMethod("findByIdentity")(identity, options);
|
|
160
|
-
},
|
|
161
|
-
syncIdentityProfile(profile, options = {}) {
|
|
162
|
-
return resolveMethod("syncIdentityProfile")(profile, options);
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
class AuthLocalServiceProvider {
|
|
168
|
-
static id = "auth.provider.local";
|
|
169
|
-
|
|
170
|
-
register(app) {
|
|
171
|
-
if (!app || typeof app.singleton !== "function" || typeof app.has !== "function") {
|
|
172
|
-
throw new Error("AuthLocalServiceProvider requires application singleton()/has().");
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
assertSelectedAuthProvider(resolveRuntimeEnv(app));
|
|
176
|
-
|
|
177
|
-
if (app.has("authService")) {
|
|
178
|
-
throw new Error("AuthLocalServiceProvider cannot register authService because another auth provider already registered it.");
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (!app.has("auth.local.backend") && resolveLocalBackendMode(app) === "file") {
|
|
182
|
-
app.singleton("auth.local.backend", (scope) => {
|
|
183
|
-
const config = resolveConfig(scope);
|
|
184
|
-
return createLocalFileBackend({
|
|
185
|
-
storeDir: config.storeDir
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
app.singleton("authService", (scope) => {
|
|
191
|
-
const config = resolveConfig(scope);
|
|
192
|
-
if (!scope.has("auth.local.backend")) {
|
|
193
|
-
throw new Error(`AUTH_LOCAL_BACKEND="${config.backend}" requires a package or app provider that registers auth.local.backend.`);
|
|
194
|
-
}
|
|
195
|
-
const backend = scope.make("auth.local.backend");
|
|
196
|
-
const profileProjector = createLazyProfileProjector(scope);
|
|
197
|
-
const invitationContextResolver = scope.has("auth.invitationContextResolver")
|
|
198
|
-
? {
|
|
199
|
-
async resolveInvitationContext(invitation, options = {}) {
|
|
200
|
-
const resolver = scope.make("auth.invitationContextResolver");
|
|
201
|
-
if (typeof resolver === "function") {
|
|
202
|
-
return resolver(invitation, options);
|
|
203
|
-
}
|
|
204
|
-
if (!resolver || typeof resolver.resolveInvitationContext !== "function") {
|
|
205
|
-
throw new Error("auth.invitationContextResolver.resolveInvitationContext() must be a function.");
|
|
206
|
-
}
|
|
207
|
-
return resolver.resolveInvitationContext(invitation, options);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
: null;
|
|
211
|
-
const passwordStrategy = scope.has("auth.local.passwordStrategy")
|
|
212
|
-
? scope.make("auth.local.passwordStrategy")
|
|
213
|
-
: null;
|
|
214
|
-
const authService = createLocalAuthService({
|
|
215
|
-
backend,
|
|
216
|
-
config,
|
|
217
|
-
profileProjector,
|
|
218
|
-
passwordStrategy,
|
|
219
|
-
invitationContextResolver
|
|
220
|
-
});
|
|
221
|
-
return applyAuthServiceDecorators(scope, authService);
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
boot(app) {
|
|
226
|
-
if (!app || typeof app.make !== "function") {
|
|
227
|
-
throw new Error("AuthLocalServiceProvider requires application make().");
|
|
228
|
-
}
|
|
229
|
-
app.make("authService");
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
export { AuthLocalServiceProvider, resolveLocalBackendMode };
|