@podosoft/podokit 1.6.3 → 1.6.4
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/dist/deploy-checks.d.ts +20 -2
- package/dist/deploy-checks.js +83 -4
- package/dist/deploy-compose-render.js +15 -4
- package/dist/deploy-compose.js +211 -27
- package/dist/deploy.js +16 -0
- package/dist/templates/fullstack/dot-agents/skills/podokit-deploy/references/profile.md +3 -2
- package/dist/templates/modules/admin-dashboard/files/tests/api/audit.api.spec.ts +26 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/auth.api.spec.ts +24 -0
- package/dist/templates/modules/admin-dashboard/files/tests/seed.setup.ts +16 -4
- package/dist/templates/modules/admin-dashboard/module.manifest.json +4 -2
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/README.md +14 -1
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-hook.spec.ts +284 -6
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-hook.ts +184 -38
- package/dist/templates/modules/auth/files/apps/api/src/auth/admin-emails.spec.ts +232 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/admin-emails.ts +68 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.ts +53 -47
- package/dist/templates/modules/auth/files/dot-claude/skills/podokit-configure-auth/SKILL.md +1 -1
- package/dist/templates/modules/auth/files/dot-claude/skills/podokit-configure-auth/references/bootstrap-admin.md +9 -2
- package/package.json +1 -1
|
@@ -17,18 +17,14 @@ import { authBaseUrl, authSecret } from "./auth-environment";
|
|
|
17
17
|
import { isMaintenanceCommand } from "./maintenance";
|
|
18
18
|
import { CLIENT_ADDRESS_HEADER } from "../core/client-address";
|
|
19
19
|
import { revokeEveryOtherSession } from "./revoke-other-sessions";
|
|
20
|
+
import { adminEmailsFrom, becomesAdministrator } from "./admin-emails";
|
|
20
21
|
// podokit:begin:auth-imports
|
|
21
22
|
// podokit:end:auth-imports
|
|
22
23
|
|
|
23
24
|
// Web origin(s) where the browser runs (WebAuthn ceremonies must match these).
|
|
24
25
|
const webOrigins = (process.env.CORS_ORIGIN ?? "http://localhost:5001").split(",").map((o) => o.trim());
|
|
25
26
|
|
|
26
|
-
const adminEmails =
|
|
27
|
-
(process.env.ADMIN_EMAILS ?? "")
|
|
28
|
-
.split(",")
|
|
29
|
-
.map((email) => email.trim().toLowerCase())
|
|
30
|
-
.filter(Boolean),
|
|
31
|
-
);
|
|
27
|
+
const adminEmails = adminEmailsFrom(process.env.ADMIN_EMAILS);
|
|
32
28
|
|
|
33
29
|
// Enabled social providers, from the resolved config (DB-first, env fallback).
|
|
34
30
|
// Admins add/edit/remove providers dynamically (Settings page), so this maps the
|
|
@@ -45,6 +41,56 @@ function buildSocial(config: AuthConfig): NonNullable<BetterAuthOptions["socialP
|
|
|
45
41
|
return providers as NonNullable<BetterAuthOptions["socialProviders"]>;
|
|
46
42
|
}
|
|
47
43
|
|
|
44
|
+
// The account and session rules of buildAuth: who may be created, who starts as
|
|
45
|
+
// an administrator or approved, and who may hold a session.
|
|
46
|
+
export function databaseHooksFor(
|
|
47
|
+
config: Pick<AuthConfig, "requireSignupApproval">,
|
|
48
|
+
administrators: ReadonlySet<string>,
|
|
49
|
+
signupOpen: () => Promise<boolean>,
|
|
50
|
+
): NonNullable<BetterAuthOptions["databaseHooks"]> {
|
|
51
|
+
return {
|
|
52
|
+
user: {
|
|
53
|
+
create: {
|
|
54
|
+
before: async (user, context) => {
|
|
55
|
+
const createdByAdmin = context?.path === "/admin/create-user";
|
|
56
|
+
assertUserCreationAllowed(await readSignupOpen(signupOpen), context?.path);
|
|
57
|
+
const adminEmail = becomesAdministrator(user, createdByAdmin, administrators);
|
|
58
|
+
return {
|
|
59
|
+
data: {
|
|
60
|
+
...user,
|
|
61
|
+
// The admin plugin contributes `role` before this hook. Preserve an
|
|
62
|
+
// explicitly requested admin-created role, while ADMIN_EMAILS
|
|
63
|
+
// addresses always become administrators.
|
|
64
|
+
...(Object.prototype.hasOwnProperty.call(user, "role")
|
|
65
|
+
? { role: adminEmail ? "admin" : user.role }
|
|
66
|
+
: {}),
|
|
67
|
+
// Existing rows are treated as approved through the field default.
|
|
68
|
+
// Only self-registration while the policy is enabled starts pending.
|
|
69
|
+
signupApproved: adminEmail || createdByAdmin || !config.requireSignupApproval,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
session: {
|
|
76
|
+
create: {
|
|
77
|
+
before: async (session, context) => {
|
|
78
|
+
if (!context) return;
|
|
79
|
+
const user = (await context.context.internalAdapter.findUserById(session.userId)) as {
|
|
80
|
+
signupApproved?: boolean | null;
|
|
81
|
+
} | null;
|
|
82
|
+
if (user?.signupApproved === false) {
|
|
83
|
+
throw APIError.from("FORBIDDEN", {
|
|
84
|
+
code: SIGNUP_APPROVAL_REQUIRED,
|
|
85
|
+
message: "Your account is waiting for administrator approval.",
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
48
94
|
// Build a better-auth instance from resolved config. Called at boot and rebuilt by
|
|
49
95
|
// auth-provider.ts when the DB config changes, so admin edits (OAuth credentials,
|
|
50
96
|
// server-enforced toggles) apply without a restart. Everything below the injection
|
|
@@ -147,47 +193,7 @@ export function buildAuth(config: AuthConfig) {
|
|
|
147
193
|
// podokit:begin:auth-plugins
|
|
148
194
|
// podokit:end:auth-plugins
|
|
149
195
|
|
|
150
|
-
const databaseHooks
|
|
151
|
-
user: {
|
|
152
|
-
create: {
|
|
153
|
-
before: async (user, context) => {
|
|
154
|
-
const adminEmail = adminEmails.has(user.email.toLowerCase());
|
|
155
|
-
const createdByAdmin = context?.path === "/admin/create-user";
|
|
156
|
-
assertUserCreationAllowed(await readSignupOpen(isSignupOpen), context?.path);
|
|
157
|
-
return {
|
|
158
|
-
data: {
|
|
159
|
-
...user,
|
|
160
|
-
// The admin plugin contributes `role` before this hook. Preserve an
|
|
161
|
-
// explicitly requested admin-created role, while bootstrap emails
|
|
162
|
-
// always become administrators.
|
|
163
|
-
...(Object.prototype.hasOwnProperty.call(user, "role")
|
|
164
|
-
? { role: adminEmail ? "admin" : user.role }
|
|
165
|
-
: {}),
|
|
166
|
-
// Existing rows are treated as approved through the field default.
|
|
167
|
-
// Only self-registration while the policy is enabled starts pending.
|
|
168
|
-
signupApproved: adminEmail || createdByAdmin || !config.requireSignupApproval,
|
|
169
|
-
},
|
|
170
|
-
};
|
|
171
|
-
},
|
|
172
|
-
},
|
|
173
|
-
},
|
|
174
|
-
session: {
|
|
175
|
-
create: {
|
|
176
|
-
before: async (session, context) => {
|
|
177
|
-
if (!context) return;
|
|
178
|
-
const user = (await context.context.internalAdapter.findUserById(session.userId)) as {
|
|
179
|
-
signupApproved?: boolean | null;
|
|
180
|
-
} | null;
|
|
181
|
-
if (user?.signupApproved === false) {
|
|
182
|
-
throw APIError.from("FORBIDDEN", {
|
|
183
|
-
code: SIGNUP_APPROVAL_REQUIRED,
|
|
184
|
-
message: "Your account is waiting for administrator approval.",
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
},
|
|
188
|
-
},
|
|
189
|
-
},
|
|
190
|
-
};
|
|
196
|
+
const databaseHooks = databaseHooksFor(config, adminEmails, isSignupOpen);
|
|
191
197
|
|
|
192
198
|
// Request-time hooks. The feature gate turns the admin Settings toggles into a
|
|
193
199
|
// real server boundary (disabled features 404) for endpoint calls, including
|
|
@@ -98,7 +98,7 @@ export MAIL_FROM="Example <noreply@example.com>"
|
|
|
98
98
|
2. Open `/login` and confirm the provider button appears.
|
|
99
99
|
3. With sign-up approval enabled, register a disposable account and confirm it reaches `/pending-approval` without a session.
|
|
100
100
|
4. Open `/admin/users`, filter by **Pending approval**, approve the account, and confirm sign-in succeeds.
|
|
101
|
-
5. Confirm administrator-created users and
|
|
101
|
+
5. Confirm administrator-created users and administrators made from `ADMIN_EMAILS` do not enter the approval queue, and that an email and password sign-up with an `ADMIN_EMAILS` address is refused.
|
|
102
102
|
6. Send a real verification or password-reset email and inspect SPF, DKIM, and DMARC results in the receiving mailbox.
|
|
103
103
|
|
|
104
104
|
## Preserve provider independence
|
|
@@ -9,8 +9,15 @@ Do not create the first administrator through the public sign-up page.
|
|
|
9
9
|
|
|
10
10
|
Set `ADMIN_EMAILS` in the same deployment environment used by the API. Include
|
|
11
11
|
the exact lowercase-normalized address selected for the initial administrator.
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
Use the administrators' real addresses: with `NODE_ENV=production` the API
|
|
13
|
+
refuses to start when an entry is not an address or is on a reserved
|
|
14
|
+
documentation name such as `example.com`, and `podo deploy doctor` fails on it.
|
|
15
|
+
|
|
16
|
+
Keep this value after bootstrap. A later account with a listed address becomes
|
|
17
|
+
an administrator only when its address is proven as the account is created: by
|
|
18
|
+
`admin:bootstrap`, an administrator, a social provider that reports the email
|
|
19
|
+
verified, a magic link, or an email code. An email and password sign-up with a
|
|
20
|
+
listed address is refused, so nobody can claim it before its owner.
|
|
14
21
|
|
|
15
22
|
## Inject credentials for one command
|
|
16
23
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@podosoft/podokit",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.4",
|
|
4
4
|
"description": "An opinionated Bun and Elysia full-stack TypeScript starter toolkit and CLI with SvelteKit, TailwindCSS, shadcn-svelte, Docker, and k3s.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|