@connextar/house 0.17.0 → 0.19.0
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/README.md +850 -0
- package/dist/admin/access.d.ts +134 -0
- package/dist/admin/access.d.ts.map +1 -0
- package/dist/admin/access.js +157 -0
- package/dist/admin/access.js.map +1 -0
- package/dist/admin/audit.d.ts +130 -0
- package/dist/admin/audit.d.ts.map +1 -0
- package/dist/admin/audit.js +132 -0
- package/dist/admin/audit.js.map +1 -0
- package/dist/admin/bootstrap.d.ts +80 -0
- package/dist/admin/bootstrap.d.ts.map +1 -0
- package/dist/admin/bootstrap.js +106 -0
- package/dist/admin/bootstrap.js.map +1 -0
- package/dist/admin/client-index.d.ts +7 -0
- package/dist/admin/client-index.d.ts.map +1 -0
- package/dist/admin/client-index.js +6 -0
- package/dist/admin/client-index.js.map +1 -0
- package/dist/admin/client.d.ts +72 -0
- package/dist/admin/client.d.ts.map +1 -0
- package/dist/admin/client.js +90 -0
- package/dist/admin/client.js.map +1 -0
- package/dist/admin/index.d.ts +13 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +13 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/proof.d.ts +34 -0
- package/dist/admin/proof.d.ts.map +1 -0
- package/dist/admin/proof.js +30 -0
- package/dist/admin/proof.js.map +1 -0
- package/dist/admin/second-factor-index.d.ts +8 -0
- package/dist/admin/second-factor-index.d.ts.map +1 -0
- package/dist/admin/second-factor-index.js +8 -0
- package/dist/admin/second-factor-index.js.map +1 -0
- package/dist/admin/second-factor.d.ts +156 -0
- package/dist/admin/second-factor.d.ts.map +1 -0
- package/dist/admin/second-factor.js +271 -0
- package/dist/admin/second-factor.js.map +1 -0
- package/dist/admin/testing-index.d.ts +6 -0
- package/dist/admin/testing-index.d.ts.map +1 -0
- package/dist/admin/testing-index.js +6 -0
- package/dist/admin/testing-index.js.map +1 -0
- package/dist/admin/testing.d.ts +84 -0
- package/dist/admin/testing.d.ts.map +1 -0
- package/dist/admin/testing.js +223 -0
- package/dist/admin/testing.js.map +1 -0
- package/dist/admin/view-as.d.ts +209 -0
- package/dist/admin/view-as.d.ts.map +1 -0
- package/dist/admin/view-as.js +251 -0
- package/dist/admin/view-as.js.map +1 -0
- package/dist/guides/guide-article.d.ts +13 -2
- package/dist/guides/guide-article.d.ts.map +1 -1
- package/dist/guides/guide-article.js +5 -3
- package/dist/guides/guide-article.js.map +1 -1
- package/dist/guides/guide-figure-zoom.d.ts +73 -0
- package/dist/guides/guide-figure-zoom.d.ts.map +1 -0
- package/dist/guides/guide-figure-zoom.js +279 -0
- package/dist/guides/guide-figure-zoom.js.map +1 -0
- package/dist/guides/guide-figure.d.ts +21 -0
- package/dist/guides/guide-figure.d.ts.map +1 -0
- package/dist/guides/guide-figure.js +19 -0
- package/dist/guides/guide-figure.js.map +1 -0
- package/dist/guides/index.d.ts +2 -0
- package/dist/guides/index.d.ts.map +1 -1
- package/dist/guides/index.js +2 -0
- package/dist/guides/index.js.map +1 -1
- package/dist/guides/library.d.ts +19 -1
- package/dist/guides/library.d.ts.map +1 -1
- package/dist/guides/library.js +9 -0
- package/dist/guides/library.js.map +1 -1
- package/dist/health/health.d.ts +65 -0
- package/dist/health/health.d.ts.map +1 -0
- package/dist/health/health.js +93 -0
- package/dist/health/health.js.map +1 -0
- package/dist/health/index.d.ts +2 -0
- package/dist/health/index.d.ts.map +1 -0
- package/dist/health/index.js +2 -0
- package/dist/health/index.js.map +1 -0
- package/dist/markdown/figure.d.ts +44 -0
- package/dist/markdown/figure.d.ts.map +1 -0
- package/dist/markdown/figure.js +56 -0
- package/dist/markdown/figure.js.map +1 -0
- package/dist/markdown/figures.d.ts +94 -0
- package/dist/markdown/figures.d.ts.map +1 -0
- package/dist/markdown/figures.js +126 -0
- package/dist/markdown/figures.js.map +1 -0
- package/dist/markdown/guide.d.ts +26 -7
- package/dist/markdown/guide.d.ts.map +1 -1
- package/dist/markdown/guide.js +27 -6
- package/dist/markdown/guide.js.map +1 -1
- package/dist/markdown/index.d.ts +2 -0
- package/dist/markdown/index.d.ts.map +1 -1
- package/dist/markdown/index.js +2 -0
- package/dist/markdown/index.js.map +1 -1
- package/dist/release/changelog.d.ts +19 -0
- package/dist/release/changelog.d.ts.map +1 -1
- package/dist/release/changelog.js +37 -6
- package/dist/release/changelog.js.map +1 -1
- package/dist/release/index.d.ts +1 -1
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +1 -1
- package/dist/release/index.js.map +1 -1
- package/dist/whats-new/body.d.ts +81 -0
- package/dist/whats-new/body.d.ts.map +1 -0
- package/dist/whats-new/body.js +29 -0
- package/dist/whats-new/body.js.map +1 -0
- package/dist/whats-new/index.d.ts +24 -0
- package/dist/whats-new/index.d.ts.map +1 -0
- package/dist/whats-new/index.js +24 -0
- package/dist/whats-new/index.js.map +1 -0
- package/dist/whats-new/seen.d.ts +23 -0
- package/dist/whats-new/seen.d.ts.map +1 -0
- package/dist/whats-new/seen.js +33 -0
- package/dist/whats-new/seen.js.map +1 -0
- package/dist/whats-new/select.d.ts +89 -0
- package/dist/whats-new/select.d.ts.map +1 -0
- package/dist/whats-new/select.js +117 -0
- package/dist/whats-new/select.js.map +1 -0
- package/dist/whats-new/use-whats-new.d.ts +36 -0
- package/dist/whats-new/use-whats-new.d.ts.map +1 -0
- package/dist/whats-new/use-whats-new.js +81 -0
- package/dist/whats-new/use-whats-new.js.map +1 -0
- package/docs/ReleaseProcess.md +12 -0
- package/package.json +25 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { type AdminAuditEvent, type AdminAuditScope, type AdminAuditSink } from "./audit.js";
|
|
2
|
+
/**
|
|
3
|
+
* Grant — or revoke — the first administrator, from a script.
|
|
4
|
+
*
|
|
5
|
+
* Nothing inside a product can make an administrator, deliberately: sign-up cannot ask for the flag and no screen
|
|
6
|
+
* grants it to yourself. That leaves exactly one gap, a fresh deployment with nobody to open the door, and every app
|
|
7
|
+
* closes it with a script. AltEd's and PM Tool's disagreed on the part that matters:
|
|
8
|
+
*
|
|
9
|
+
* - **An administrator already exists → refused**, unless forced. A second one is ordinary work for the first,
|
|
10
|
+
* through a screen where it is audited and attributed. Both.
|
|
11
|
+
* - **The account must already have two-factor on → refused otherwise** (PM Tool). An administrator account without a
|
|
12
|
+
* second factor can be enrolled by whoever first holds its mailbox or a session, and the code they enrolled would
|
|
13
|
+
* then open administration. So the person signs up and turns two-factor on first, then this is run. AltEd promoted
|
|
14
|
+
* anybody, and created the account if it was missing.
|
|
15
|
+
* - **The account must exist, be verified, and not be deleted** (PM Tool). An unverified row may be somebody else's
|
|
16
|
+
* sign-up with this address and a password of their choosing.
|
|
17
|
+
* - **Every run is recorded** — the grant, the revoke, and the refusal. AltEd recorded its grant; PM Tool nothing.
|
|
18
|
+
*
|
|
19
|
+
* `allowWithoutTwoFactor` is the documented first-run escape, for a deployment whose only way to turn two-factor on is
|
|
20
|
+
* behind administration. It works only while there is no administrator at all, and the event says it was used.
|
|
21
|
+
* `adminAccess` still refuses that administrator until they turn two-factor on and sign in with it.
|
|
22
|
+
*/
|
|
23
|
+
export interface BootstrapUser {
|
|
24
|
+
id: string;
|
|
25
|
+
email: string;
|
|
26
|
+
name?: string | null;
|
|
27
|
+
isAdmin: boolean;
|
|
28
|
+
/** Omit when the app has no verification step. */
|
|
29
|
+
emailVerified?: boolean | null;
|
|
30
|
+
/** Soft-deleted. */
|
|
31
|
+
deleted?: boolean | null;
|
|
32
|
+
}
|
|
33
|
+
export interface BootstrapAdminDeps {
|
|
34
|
+
/** By email, lower-cased. */
|
|
35
|
+
findUser(email: string): Promise<BootstrapUser | null | undefined>;
|
|
36
|
+
/** Administrators that exist now, not counting deleted accounts. */
|
|
37
|
+
countAdmins(): Promise<number>;
|
|
38
|
+
setAdmin(userId: string, isAdmin: boolean): Promise<void>;
|
|
39
|
+
twoFactorEnabled(userId: string): Promise<boolean | null | undefined>;
|
|
40
|
+
audit: AdminAuditSink;
|
|
41
|
+
}
|
|
42
|
+
export interface BootstrapAdminRequest {
|
|
43
|
+
email: string;
|
|
44
|
+
/** Grant although an administrator exists. */
|
|
45
|
+
force?: boolean;
|
|
46
|
+
/** Take the standing away instead. */
|
|
47
|
+
revoke?: boolean;
|
|
48
|
+
/** The first-run escape. See above. */
|
|
49
|
+
allowWithoutTwoFactor?: boolean;
|
|
50
|
+
/** The standing's name in the event: `"platform-admin"`, `"ADMIN"`. Default `"admin"`. */
|
|
51
|
+
standing?: string;
|
|
52
|
+
/** What the account had before, in the event. Default `"user"`. */
|
|
53
|
+
previousStanding?: string;
|
|
54
|
+
/** Default platform. */
|
|
55
|
+
scope?: AdminAuditScope;
|
|
56
|
+
/** The script's name in the event. Default `"bootstrap-admin"`. */
|
|
57
|
+
via?: string;
|
|
58
|
+
}
|
|
59
|
+
export type BootstrapRefusalReason = "invalid-email" | "no-account" | "deleted" | "unverified" | "already-admin" | "not-admin" | "admin-exists" | "two-factor-off" | "escape-not-first-run";
|
|
60
|
+
export type BootstrapAdminResult = {
|
|
61
|
+
ok: true;
|
|
62
|
+
outcome: "granted" | "revoked";
|
|
63
|
+
user: BootstrapUser;
|
|
64
|
+
message: string;
|
|
65
|
+
event: AdminAuditEvent;
|
|
66
|
+
} | {
|
|
67
|
+
ok: false;
|
|
68
|
+
reason: BootstrapRefusalReason;
|
|
69
|
+
message: string;
|
|
70
|
+
event: AdminAuditEvent;
|
|
71
|
+
};
|
|
72
|
+
export declare function bootstrapAdmin(deps: BootstrapAdminDeps, request: BootstrapAdminRequest): Promise<BootstrapAdminResult>;
|
|
73
|
+
/**
|
|
74
|
+
* The script's arguments: `you@example.com`, `--force`, `--revoke`, `--allow-without-two-factor`. Anything else
|
|
75
|
+
* starting with `--` is an error, so a typo never quietly grants without the flag that was meant.
|
|
76
|
+
*/
|
|
77
|
+
export declare function bootstrapAdminArgs(argv: readonly string[]): BootstrapAdminRequest & {
|
|
78
|
+
rest: string[];
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=bootstrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../src/admin/bootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAE1G;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,kDAAkD;IAClD,aAAa,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/B,oBAAoB;IACpB,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,6BAA6B;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACnE,oEAAoE;IACpE,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACtE,KAAK,EAAE,cAAc,CAAC;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,8CAA8C;IAC9C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,sCAAsC;IACtC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,uCAAuC;IACvC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wBAAwB;IACxB,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,mEAAmE;IACnE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,sBAAsB,GAC9B,eAAe,GACf,YAAY,GACZ,SAAS,GACT,YAAY,GACZ,eAAe,GACf,WAAW,GACX,cAAc,GACd,gBAAgB,GAChB,sBAAsB,CAAC;AAE3B,MAAM,MAAM,oBAAoB,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,SAAS,GAAG,SAAS,CAAC;IAAC,IAAI,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,eAAe,CAAA;CAAE,GAC1G;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,sBAAsB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,eAAe,CAAA;CAAE,CAAC;AAE3F,wBAAsB,cAAc,CAClC,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAwH/B;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,qBAAqB,GAAG;IAAE,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAYtG"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { roleChanged } from "./audit.js";
|
|
2
|
+
export async function bootstrapAdmin(deps, request) {
|
|
3
|
+
const email = request.email?.trim().toLowerCase() ?? "";
|
|
4
|
+
const standing = request.standing ?? "admin";
|
|
5
|
+
const previous = request.previousStanding ?? "user";
|
|
6
|
+
const via = request.via ?? "bootstrap-admin";
|
|
7
|
+
const scope = request.scope ?? { kind: "platform" };
|
|
8
|
+
async function refuse(reason, message, user) {
|
|
9
|
+
const event = {
|
|
10
|
+
action: "admin.bootstrap_refused",
|
|
11
|
+
actor: null,
|
|
12
|
+
scope,
|
|
13
|
+
entity: { type: "user", id: user?.id ?? null },
|
|
14
|
+
summary: `Refused to ${request.revoke ? "revoke" : "grant"} ${standing} for ${email || "(no email)"} (${via}): ${reason}`.slice(0, 300),
|
|
15
|
+
changes: { reason, email, revoke: !!request.revoke, force: !!request.force, via },
|
|
16
|
+
at: new Date(),
|
|
17
|
+
};
|
|
18
|
+
await record(event);
|
|
19
|
+
return { ok: false, reason, message, event };
|
|
20
|
+
}
|
|
21
|
+
async function record(event) {
|
|
22
|
+
try {
|
|
23
|
+
await deps.audit(event);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
// A script is run by a person watching it: say so loudly, rather than swallow it as a request would.
|
|
27
|
+
throw new Error(`[admin] ${event.action} could not be recorded in the audit trail: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (!email || !/^[^\s@]+@[^\s@]+$/.test(email)) {
|
|
31
|
+
return refuse("invalid-email", "Give the email address of the account to make an administrator.");
|
|
32
|
+
}
|
|
33
|
+
const user = await deps.findUser(email);
|
|
34
|
+
if (!user) {
|
|
35
|
+
return refuse("no-account", `No account for ${email}. They sign up first, turn on two-factor authentication in their security settings, then run this again.`);
|
|
36
|
+
}
|
|
37
|
+
if (user.deleted)
|
|
38
|
+
return refuse("deleted", `${email} belongs to a deleted account. Restore it first.`, user);
|
|
39
|
+
if (request.revoke) {
|
|
40
|
+
if (!user.isAdmin)
|
|
41
|
+
return refuse("not-admin", `${email} is not an administrator.`, user);
|
|
42
|
+
await deps.setAdmin(user.id, false);
|
|
43
|
+
const event = roleChanged({ actor: null, target: user, from: standing, to: previous, scope, via });
|
|
44
|
+
await record(event);
|
|
45
|
+
return { ok: true, outcome: "revoked", user, message: `${email} is no longer an administrator.`, event };
|
|
46
|
+
}
|
|
47
|
+
if (user.isAdmin)
|
|
48
|
+
return refuse("already-admin", `${email} is already an administrator. Nothing was changed.`, user);
|
|
49
|
+
if (user.emailVerified === false) {
|
|
50
|
+
return refuse("unverified", `${email} has not been verified. Sign in once with a link to that address, then run this again.`, user);
|
|
51
|
+
}
|
|
52
|
+
const admins = await deps.countAdmins();
|
|
53
|
+
if (admins > 0 && !request.force) {
|
|
54
|
+
return refuse("admin-exists", `This deployment already has ${admins} administrator${admins === 1 ? "" : "s"}. Grant further administrators ` +
|
|
55
|
+
"from the screen built for it, where the change is audited. Pass --force only if you are locked out.", user);
|
|
56
|
+
}
|
|
57
|
+
const twoFactor = (await deps.twoFactorEnabled(user.id)) === true;
|
|
58
|
+
const escape = !twoFactor && request.allowWithoutTwoFactor === true;
|
|
59
|
+
if (!twoFactor) {
|
|
60
|
+
if (!request.allowWithoutTwoFactor) {
|
|
61
|
+
return refuse("two-factor-off", `${email} hasn't turned on two-factor authentication. They turn it on first, then run this again.`, user);
|
|
62
|
+
}
|
|
63
|
+
if (admins > 0) {
|
|
64
|
+
return refuse("escape-not-first-run", "--allow-without-two-factor is only for the first administrator of a deployment, and this one has one.", user);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
await deps.setAdmin(user.id, true);
|
|
68
|
+
const event = roleChanged({
|
|
69
|
+
actor: null,
|
|
70
|
+
target: user,
|
|
71
|
+
from: previous,
|
|
72
|
+
to: standing,
|
|
73
|
+
scope,
|
|
74
|
+
via,
|
|
75
|
+
changes: { forced: !!request.force, ...(escape ? { withoutTwoFactor: true } : {}) },
|
|
76
|
+
});
|
|
77
|
+
await record(event);
|
|
78
|
+
return {
|
|
79
|
+
ok: true,
|
|
80
|
+
outcome: "granted",
|
|
81
|
+
user,
|
|
82
|
+
message: escape
|
|
83
|
+
? `${email} is now an administrator, without two-factor (first-run escape). Administration stays closed to them until they turn it on and sign in with it.`
|
|
84
|
+
: `${email} is now an administrator. They sign in with their password and code, or a passkey, to open administration.`,
|
|
85
|
+
event,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The script's arguments: `you@example.com`, `--force`, `--revoke`, `--allow-without-two-factor`. Anything else
|
|
90
|
+
* starting with `--` is an error, so a typo never quietly grants without the flag that was meant.
|
|
91
|
+
*/
|
|
92
|
+
export function bootstrapAdminArgs(argv) {
|
|
93
|
+
const flags = new Set(["--force", "--revoke", "--allow-without-two-factor"]);
|
|
94
|
+
const unknown = argv.filter((arg) => arg.startsWith("--") && !flags.has(arg));
|
|
95
|
+
if (unknown.length)
|
|
96
|
+
throw new Error(`Unknown option${unknown.length === 1 ? "" : "s"}: ${unknown.join(", ")}`);
|
|
97
|
+
const [email = "", ...rest] = argv.filter((arg) => !arg.startsWith("--"));
|
|
98
|
+
return {
|
|
99
|
+
email,
|
|
100
|
+
force: argv.includes("--force"),
|
|
101
|
+
revoke: argv.includes("--revoke"),
|
|
102
|
+
allowWithoutTwoFactor: argv.includes("--allow-without-two-factor"),
|
|
103
|
+
rest,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=bootstrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../../src/admin/bootstrap.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@connextar/house/admin/client` — the refusal notice and the "view as" banner, on the shared theme tokens. Imports
|
|
3
|
+
* nothing of the server half at runtime: the refusal's type only.
|
|
4
|
+
*/
|
|
5
|
+
export { ADMIN_REFUSAL_ACTION_LABELS, ADMIN_REFUSAL_NOTICE_CLASS_NAMES, ADMIN_REFUSAL_TITLES, AdminRefusalNotice, VIEWING_AS_BANNER_CLASS_NAMES, VIEWING_AS_BANNER_LABELS, ViewingAsBanner, type AdminRefusalNoticeClassNames, type AdminRefusalNoticeProps, type ViewingAsBannerClassNames, type ViewingAsBannerLabels, type ViewingAsBannerProps, } from "./client.js";
|
|
6
|
+
export type { AdminRefusal, AdminRefusalReason } from "./access.js";
|
|
7
|
+
//# sourceMappingURL=client-index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-index.d.ts","sourceRoot":"","sources":["../../src/admin/client-index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,2BAA2B,EAC3B,gCAAgC,EAChC,oBAAoB,EACpB,kBAAkB,EAClB,6BAA6B,EAC7B,wBAAwB,EACxB,eAAe,EACf,KAAK,4BAA4B,EACjC,KAAK,uBAAuB,EAC5B,KAAK,yBAAyB,EAC9B,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@connextar/house/admin/client` — the refusal notice and the "view as" banner, on the shared theme tokens. Imports
|
|
3
|
+
* nothing of the server half at runtime: the refusal's type only.
|
|
4
|
+
*/
|
|
5
|
+
export { ADMIN_REFUSAL_ACTION_LABELS, ADMIN_REFUSAL_NOTICE_CLASS_NAMES, ADMIN_REFUSAL_TITLES, AdminRefusalNotice, VIEWING_AS_BANNER_CLASS_NAMES, VIEWING_AS_BANNER_LABELS, ViewingAsBanner, } from "./client.js";
|
|
6
|
+
//# sourceMappingURL=client-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-index.js","sourceRoot":"","sources":["../../src/admin/client-index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,2BAA2B,EAC3B,gCAAgC,EAChC,oBAAoB,EACpB,kBAAkB,EAClB,6BAA6B,EAC7B,wBAAwB,EACxB,eAAe,GAMhB,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { AdminRefusal, AdminRefusalReason } from "./access.js";
|
|
3
|
+
/** What the notice's link says, per reason. */
|
|
4
|
+
export declare const ADMIN_REFUSAL_ACTION_LABELS: Record<AdminRefusalReason, string>;
|
|
5
|
+
export declare const ADMIN_REFUSAL_TITLES: Record<AdminRefusalReason, string>;
|
|
6
|
+
export interface AdminRefusalNoticeClassNames {
|
|
7
|
+
root: string;
|
|
8
|
+
icon: string;
|
|
9
|
+
body: string;
|
|
10
|
+
title: string;
|
|
11
|
+
message: string;
|
|
12
|
+
action: string;
|
|
13
|
+
}
|
|
14
|
+
/** The house look, on the shared theme tokens. */
|
|
15
|
+
export declare const ADMIN_REFUSAL_NOTICE_CLASS_NAMES: AdminRefusalNoticeClassNames;
|
|
16
|
+
export interface AdminRefusalNoticeProps {
|
|
17
|
+
/** From `adminAccess().check()`, `adminRefusalFromParam(searchParams.admin)`, or a refused route's answer. */
|
|
18
|
+
refusal: Pick<AdminRefusal, "reason" | "message" | "fixHref"> | null | undefined;
|
|
19
|
+
/** The link's words, per reason. */
|
|
20
|
+
labels?: Partial<Record<AdminRefusalReason, string>>;
|
|
21
|
+
/** The heading, per reason. */
|
|
22
|
+
titles?: Partial<Record<AdminRefusalReason, string>>;
|
|
23
|
+
className?: string;
|
|
24
|
+
classNames?: Partial<AdminRefusalNoticeClassNames>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Why administration is refused, in the same words the routes refuse with, and the one thing to do about it. Draws
|
|
28
|
+
* nothing without a refusal, so a settings page can render it unconditionally from its `?admin=` parameter.
|
|
29
|
+
*/
|
|
30
|
+
export declare function AdminRefusalNotice({ refusal, labels, titles, className, classNames }: AdminRefusalNoticeProps): React.JSX.Element | null;
|
|
31
|
+
export interface ViewingAsBannerClassNames {
|
|
32
|
+
root: string;
|
|
33
|
+
icon: string;
|
|
34
|
+
message: string;
|
|
35
|
+
until: string;
|
|
36
|
+
stop: string;
|
|
37
|
+
}
|
|
38
|
+
export declare const VIEWING_AS_BANNER_CLASS_NAMES: ViewingAsBannerClassNames;
|
|
39
|
+
export interface ViewingAsBannerLabels {
|
|
40
|
+
/** Default: "You’re viewing as {name}. Nothing can be changed until you stop." */
|
|
41
|
+
message: (name: string) => string;
|
|
42
|
+
/** Default: "Ends at {time}". */
|
|
43
|
+
until: (time: string) => string;
|
|
44
|
+
stop: string;
|
|
45
|
+
stopping: string;
|
|
46
|
+
/** The error toast's title when stopping fails. */
|
|
47
|
+
failed: string;
|
|
48
|
+
}
|
|
49
|
+
export declare const VIEWING_AS_BANNER_LABELS: ViewingAsBannerLabels;
|
|
50
|
+
export interface ViewingAsBannerProps {
|
|
51
|
+
/** The person being viewed. */
|
|
52
|
+
name: string;
|
|
53
|
+
/** When the view lapses on its own. */
|
|
54
|
+
expiresAt?: Date | string | null;
|
|
55
|
+
/**
|
|
56
|
+
* End the view — `DELETE /api/admin/view-as`, then a reload or `router.refresh()`. Throw to refuse: the banner stays
|
|
57
|
+
* and the reason is reported through the app's `ActionFeedbackProvider`.
|
|
58
|
+
*/
|
|
59
|
+
onStop: () => void | Promise<void>;
|
|
60
|
+
labels?: Partial<ViewingAsBannerLabels>;
|
|
61
|
+
/** For the end time. Default the browser's. */
|
|
62
|
+
locale?: string;
|
|
63
|
+
timeZone?: string;
|
|
64
|
+
className?: string;
|
|
65
|
+
classNames?: Partial<ViewingAsBannerClassNames>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Above every page while a view is in force, and it cannot be dismissed: the only way to make it go is to stop.
|
|
69
|
+
* Loud on purpose — the screens below look exactly like the person's own.
|
|
70
|
+
*/
|
|
71
|
+
export declare function ViewingAsBanner({ name, expiresAt, onStop, labels, locale, timeZone, className, classNames, }: ViewingAsBannerProps): React.JSX.Element;
|
|
72
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/admin/client.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEpE,+CAA+C;AAC/C,eAAO,MAAM,2BAA2B,EAAE,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAO1E,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAOnE,CAAC;AAEF,MAAM,WAAW,4BAA4B;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,kDAAkD;AAClD,eAAO,MAAM,gCAAgC,EAAE,4BAQ9C,CAAC;AAEF,MAAM,WAAW,uBAAuB;IACtC,8GAA8G;IAC9G,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;IACjF,oCAAoC;IACpC,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC,CAAC;IACrD,+BAA+B;IAC/B,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC,CAAC;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC,4BAA4B,CAAC,CAAC;CACpD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,EAAE,uBAAuB,4BAmB7G;AAED,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,6BAA6B,EAAE,yBAM3C,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC,kFAAkF;IAClF,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAClC,iCAAiC;IACjC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,wBAAwB,EAAE,qBAMtC,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACnC,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;IACjC;;;OAGG;IACH,MAAM,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACxC,+CAA+C;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC,yBAAyB,CAAC,CAAC;CACjD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAC9B,IAAI,EACJ,SAAS,EACT,MAAM,EACN,MAAM,EACN,MAAM,EACN,QAAQ,EACR,SAAS,EACT,UAAU,GACX,EAAE,oBAAoB,qBAoCtB"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Eye, ShieldAlert } from "lucide-react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { cx } from "../internal/cx.js";
|
|
6
|
+
import { useActionReporter } from "../ux/feedback.js";
|
|
7
|
+
/** What the notice's link says, per reason. */
|
|
8
|
+
export const ADMIN_REFUSAL_ACTION_LABELS = {
|
|
9
|
+
"signed-out": "Sign in",
|
|
10
|
+
"session-ended": "Sign in again",
|
|
11
|
+
"not-admin": "Go back",
|
|
12
|
+
"two-factor-off": "Set up two-factor",
|
|
13
|
+
"sign-in-without-second-factor": "Sign in with your code",
|
|
14
|
+
"viewing-as": "Stop viewing",
|
|
15
|
+
};
|
|
16
|
+
export const ADMIN_REFUSAL_TITLES = {
|
|
17
|
+
"signed-out": "You’re signed out",
|
|
18
|
+
"session-ended": "This sign-in has ended",
|
|
19
|
+
"not-admin": "Administrators only",
|
|
20
|
+
"two-factor-off": "Administration needs two-factor",
|
|
21
|
+
"sign-in-without-second-factor": "Sign in with your second factor",
|
|
22
|
+
"viewing-as": "You’re viewing as somebody else",
|
|
23
|
+
};
|
|
24
|
+
/** The house look, on the shared theme tokens. */
|
|
25
|
+
export const ADMIN_REFUSAL_NOTICE_CLASS_NAMES = {
|
|
26
|
+
root: "flex items-start gap-3 rounded-lg border border-destructive/40 bg-destructive/5 p-4 text-sm",
|
|
27
|
+
icon: "mt-0.5 size-4 shrink-0 text-destructive",
|
|
28
|
+
body: "flex min-w-0 flex-1 flex-col gap-1",
|
|
29
|
+
title: "font-medium text-foreground",
|
|
30
|
+
message: "text-muted-foreground",
|
|
31
|
+
action: "mt-1 inline-flex w-fit items-center rounded-md border border-border px-2.5 py-1 text-xs font-medium text-foreground transition-colors hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Why administration is refused, in the same words the routes refuse with, and the one thing to do about it. Draws
|
|
35
|
+
* nothing without a refusal, so a settings page can render it unconditionally from its `?admin=` parameter.
|
|
36
|
+
*/
|
|
37
|
+
export function AdminRefusalNotice({ refusal, labels, titles, className, classNames }) {
|
|
38
|
+
if (!refusal)
|
|
39
|
+
return null;
|
|
40
|
+
const c = { ...ADMIN_REFUSAL_NOTICE_CLASS_NAMES, ...classNames };
|
|
41
|
+
const title = titles?.[refusal.reason] ?? ADMIN_REFUSAL_TITLES[refusal.reason];
|
|
42
|
+
const label = labels?.[refusal.reason] ?? ADMIN_REFUSAL_ACTION_LABELS[refusal.reason];
|
|
43
|
+
return (_jsxs("div", { role: "alert", "data-reason": refusal.reason, className: cx(c.root, className), children: [_jsx(ShieldAlert, { "aria-hidden": "true", className: c.icon }), _jsxs("div", { className: c.body, children: [_jsx("p", { className: c.title, children: title }), _jsx("p", { className: c.message, children: refusal.message }), refusal.fixHref ? (_jsx("a", { href: refusal.fixHref, className: c.action, children: label })) : null] })] }));
|
|
44
|
+
}
|
|
45
|
+
export const VIEWING_AS_BANNER_CLASS_NAMES = {
|
|
46
|
+
root: "sticky top-0 z-50 flex flex-wrap items-center justify-center gap-x-3 gap-y-1 border-b border-primary/30 bg-primary px-4 py-2 text-sm text-primary-foreground print:hidden",
|
|
47
|
+
icon: "size-4 shrink-0",
|
|
48
|
+
message: "font-medium",
|
|
49
|
+
until: "text-primary-foreground/80",
|
|
50
|
+
stop: "inline-flex items-center rounded-md border border-primary-foreground/40 px-2.5 py-0.5 text-xs font-medium transition-colors hover:bg-primary-foreground/10 focus-visible:ring-2 focus-visible:ring-primary-foreground focus-visible:outline-none disabled:pointer-events-none disabled:opacity-60",
|
|
51
|
+
};
|
|
52
|
+
export const VIEWING_AS_BANNER_LABELS = {
|
|
53
|
+
message: (name) => `You’re viewing as ${name}. Nothing can be changed until you stop.`,
|
|
54
|
+
until: (time) => `Ends at ${time}`,
|
|
55
|
+
stop: "Stop viewing",
|
|
56
|
+
stopping: "Stopping…",
|
|
57
|
+
failed: "Could not stop viewing",
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Above every page while a view is in force, and it cannot be dismissed: the only way to make it go is to stop.
|
|
61
|
+
* Loud on purpose — the screens below look exactly like the person's own.
|
|
62
|
+
*/
|
|
63
|
+
export function ViewingAsBanner({ name, expiresAt, onStop, labels, locale, timeZone, className, classNames, }) {
|
|
64
|
+
const c = { ...VIEWING_AS_BANNER_CLASS_NAMES, ...classNames };
|
|
65
|
+
const words = { ...VIEWING_AS_BANNER_LABELS, ...labels };
|
|
66
|
+
const report = useActionReporter();
|
|
67
|
+
const [stopping, setStopping] = React.useState(false);
|
|
68
|
+
const until = expiresAt ? new Date(expiresAt) : null;
|
|
69
|
+
const time = until && !Number.isNaN(until.getTime())
|
|
70
|
+
? until.toLocaleTimeString(locale, { hour: "numeric", minute: "2-digit", ...(timeZone ? { timeZone } : {}) })
|
|
71
|
+
: null;
|
|
72
|
+
async function stop() {
|
|
73
|
+
setStopping(true);
|
|
74
|
+
try {
|
|
75
|
+
await onStop();
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
report({
|
|
79
|
+
tone: "error",
|
|
80
|
+
title: words.failed,
|
|
81
|
+
...(error instanceof Error && error.message ? { description: error.message } : {}),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
setStopping(false);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return (_jsxs("div", { role: "status", "aria-live": "polite", "data-viewing-as": "", className: cx(c.root, className), children: [_jsx(Eye, { "aria-hidden": "true", className: c.icon }), _jsx("span", { className: c.message, children: words.message(name) }), time ? _jsx("span", { className: c.until, children: words.until(time) }) : null, _jsx("button", { type: "button", className: c.stop, onClick: () => void stop(), disabled: stopping, "aria-busy": stopping, children: stopping ? words.stopping : words.stop })] }));
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/admin/client.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@connextar/house/admin` — the server half of administration: who may use it, "view as", the audit event shape and
|
|
3
|
+
* the bootstrap script's rules. Imports no Better Auth and no Prisma — every lookup is injected — and nothing but
|
|
4
|
+
* `node:crypto`, so an app with its own auth (Canisters) takes the decision, the audit shape and "view as" as they
|
|
5
|
+
* are. The Better Auth plugin that records second-factor sign-ins is `@connextar/house/admin/second-factor`; the
|
|
6
|
+
* notice and the banner are `@connextar/house/admin/client`; the guard-coverage sweep is `@connextar/house/admin/testing`.
|
|
7
|
+
*/
|
|
8
|
+
export { ADMIN_REFUSAL_REASONS, AdminRefusalError, DEFAULT_ADMIN_REFUSAL_WORDS, adminAccess, adminRefusal, adminRefusalFromParam, assertAdminAccess, decideAdminAccess, type AdminAccess, type AdminAccessDecision, type AdminAccessFacts, type AdminAccessLookups, type AdminAccessOptions, type AdminRefusal, type AdminRefusalReason, type AdminRefusalWording, type AdminRefusalWords, type SignedIn, } from "./access.js";
|
|
9
|
+
export { PLATFORM_SCOPE, SUMMARY_MAX, adminSignedIn, emitSafely, platformAction, requestMeta, roleChanged, viewAsStarted, viewAsStopped, type AdminAuditActor, type AdminAuditEvent, type AdminAuditScope, type AdminAuditSink, type AdminSignInMethod, type AuditLogger, type RequestMeta, } from "./audit.js";
|
|
10
|
+
export { bootstrapAdmin, bootstrapAdminArgs, type BootstrapAdminDeps, type BootstrapAdminRequest, type BootstrapAdminResult, type BootstrapRefusalReason, type BootstrapUser, } from "./bootstrap.js";
|
|
11
|
+
export { DEFAULT_ADMIN_PROOF_PREFIX, adminProofIdentifier, isLiveProof, sessionProofReader, type AuthWithProofs, type ProofRow, } from "./proof.js";
|
|
12
|
+
export { PRISMA_WRITE_OPERATIONS, allowWhileViewing, refuseWhileViewingAs, viewAs, viewAsWriteGuard, viewingAsGate, viewingAsMessage, type CookieJar, type MaybeViewing, type PrismaOperationParams, type RealAdmin, type RefuseWhileViewingAsOptions, type ViewAs, type ViewAsCookieOptions, type ViewAsOptions, type ViewAsStartRefusal, type ViewAsStartResult, type ViewAsWriteGuardOptions, type ViewingAs, type ViewingAsGate, type ViewingAsGateOptions, type ViewingLookup, } from "./view-as.js";
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,2BAA2B,EAC3B,WAAW,EACX,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,QAAQ,GACd,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,cAAc,EACd,WAAW,EACX,aAAa,EACb,UAAU,EACV,cAAc,EACd,WAAW,EACX,WAAW,EACX,aAAa,EACb,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,WAAW,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,aAAa,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,WAAW,EACX,kBAAkB,EAClB,KAAK,cAAc,EACnB,KAAK,QAAQ,GACd,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,oBAAoB,EACpB,MAAM,EACN,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAC1B,KAAK,SAAS,EACd,KAAK,2BAA2B,EAChC,KAAK,MAAM,EACX,KAAK,mBAAmB,EACxB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,aAAa,GACnB,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@connextar/house/admin` — the server half of administration: who may use it, "view as", the audit event shape and
|
|
3
|
+
* the bootstrap script's rules. Imports no Better Auth and no Prisma — every lookup is injected — and nothing but
|
|
4
|
+
* `node:crypto`, so an app with its own auth (Canisters) takes the decision, the audit shape and "view as" as they
|
|
5
|
+
* are. The Better Auth plugin that records second-factor sign-ins is `@connextar/house/admin/second-factor`; the
|
|
6
|
+
* notice and the banner are `@connextar/house/admin/client`; the guard-coverage sweep is `@connextar/house/admin/testing`.
|
|
7
|
+
*/
|
|
8
|
+
export { ADMIN_REFUSAL_REASONS, AdminRefusalError, DEFAULT_ADMIN_REFUSAL_WORDS, adminAccess, adminRefusal, adminRefusalFromParam, assertAdminAccess, decideAdminAccess, } from "./access.js";
|
|
9
|
+
export { PLATFORM_SCOPE, SUMMARY_MAX, adminSignedIn, emitSafely, platformAction, requestMeta, roleChanged, viewAsStarted, viewAsStopped, } from "./audit.js";
|
|
10
|
+
export { bootstrapAdmin, bootstrapAdminArgs, } from "./bootstrap.js";
|
|
11
|
+
export { DEFAULT_ADMIN_PROOF_PREFIX, adminProofIdentifier, isLiveProof, sessionProofReader, } from "./proof.js";
|
|
12
|
+
export { PRISMA_WRITE_OPERATIONS, allowWhileViewing, refuseWhileViewingAs, viewAs, viewAsWriteGuard, viewingAsGate, viewingAsMessage, } from "./view-as.js";
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,2BAA2B,EAC3B,WAAW,EACX,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,GAWlB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,cAAc,EACd,WAAW,EACX,aAAa,EACb,UAAU,EACV,cAAc,EACd,WAAW,EACX,WAAW,EACX,aAAa,EACb,aAAa,GAQd,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,cAAc,EACd,kBAAkB,GAMnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,WAAW,EACX,kBAAkB,GAGnB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,oBAAoB,EACpB,MAAM,EACN,gBAAgB,EAChB,aAAa,EACb,gBAAgB,GAgBjB,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The record that says a session was made with a second factor — shared by the Better Auth plugin that writes it
|
|
3
|
+
* (`@connextar/house/admin/second-factor`) and `adminAccess`, which reads it. Nothing here imports Better Auth: the
|
|
4
|
+
* reader takes the instance structurally, so `@connextar/house/admin` loads in an app without it.
|
|
5
|
+
*
|
|
6
|
+
* It is a row in Better Auth's own `verification` table — `<prefix><session id>`, the user's id as its value,
|
|
7
|
+
* expiring with the session it was written for — the table, and the shape, Better Auth keeps its trusted-device
|
|
8
|
+
* records in. Six apps wrote one each, under three prefixes; an app adopting the shared plugin passes its own prefix,
|
|
9
|
+
* so the sessions it already recorded keep counting.
|
|
10
|
+
*/
|
|
11
|
+
/** AltEd's and PharmaLine's prefix. Quickie's is `operator-second-factor-`; senco's, PM Tool's and FB Web's `second-factor-session:`. */
|
|
12
|
+
export declare const DEFAULT_ADMIN_PROOF_PREFIX = "admin-second-factor:";
|
|
13
|
+
export declare const adminProofIdentifier: (prefix: string, sessionId: string) => string;
|
|
14
|
+
/** A `verification` row, as Better Auth's internal adapter answers it. */
|
|
15
|
+
export interface ProofRow {
|
|
16
|
+
value: string;
|
|
17
|
+
expiresAt: Date | string;
|
|
18
|
+
}
|
|
19
|
+
/** The part of a Better Auth instance the reader needs: `auth.$context` and its internal adapter. */
|
|
20
|
+
export interface AuthWithProofs {
|
|
21
|
+
$context: PromiseLike<{
|
|
22
|
+
internalAdapter: {
|
|
23
|
+
findVerificationValue(identifier: string): Promise<ProofRow | null | undefined>;
|
|
24
|
+
};
|
|
25
|
+
}>;
|
|
26
|
+
}
|
|
27
|
+
/** Whether a row is a live proof for this user: it names them and has not expired. */
|
|
28
|
+
export declare function isLiveProof(row: ProofRow | null | undefined, userId: string, now?: number): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* `sessionProven(sessionId, userId)` read from the plugin's record through Better Auth's own adapter, so it agrees
|
|
31
|
+
* with the plugin about where and how the record is stored.
|
|
32
|
+
*/
|
|
33
|
+
export declare function sessionProofReader(auth: AuthWithProofs, recordPrefix?: string): (sessionId: string, userId: string) => Promise<boolean>;
|
|
34
|
+
//# sourceMappingURL=proof.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proof.d.ts","sourceRoot":"","sources":["../../src/admin/proof.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,yIAAyI;AACzI,eAAO,MAAM,0BAA0B,yBAAyB,CAAC;AAEjE,eAAO,MAAM,oBAAoB,GAAI,QAAQ,MAAM,EAAE,WAAW,MAAM,WAA4B,CAAC;AAEnG,0EAA0E;AAC1E,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,GAAG,MAAM,CAAC;CAC1B;AAED,qGAAqG;AACrG,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,WAAW,CAAC;QACpB,eAAe,EAAE;YAAE,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;SAAE,CAAC;KACtG,CAAC,CAAC;CACJ;AAID,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,OAAO,CAEvG;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,cAAc,EAAE,YAAY,GAAE,MAAmC,IAC1F,WAAW,MAAM,EAAE,QAAQ,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC,CAKnE"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The record that says a session was made with a second factor — shared by the Better Auth plugin that writes it
|
|
3
|
+
* (`@connextar/house/admin/second-factor`) and `adminAccess`, which reads it. Nothing here imports Better Auth: the
|
|
4
|
+
* reader takes the instance structurally, so `@connextar/house/admin` loads in an app without it.
|
|
5
|
+
*
|
|
6
|
+
* It is a row in Better Auth's own `verification` table — `<prefix><session id>`, the user's id as its value,
|
|
7
|
+
* expiring with the session it was written for — the table, and the shape, Better Auth keeps its trusted-device
|
|
8
|
+
* records in. Six apps wrote one each, under three prefixes; an app adopting the shared plugin passes its own prefix,
|
|
9
|
+
* so the sessions it already recorded keep counting.
|
|
10
|
+
*/
|
|
11
|
+
/** AltEd's and PharmaLine's prefix. Quickie's is `operator-second-factor-`; senco's, PM Tool's and FB Web's `second-factor-session:`. */
|
|
12
|
+
export const DEFAULT_ADMIN_PROOF_PREFIX = "admin-second-factor:";
|
|
13
|
+
export const adminProofIdentifier = (prefix, sessionId) => `${prefix}${sessionId}`;
|
|
14
|
+
const toTime = (value) => (value instanceof Date ? value.getTime() : new Date(value).getTime());
|
|
15
|
+
/** Whether a row is a live proof for this user: it names them and has not expired. */
|
|
16
|
+
export function isLiveProof(row, userId, now = Date.now()) {
|
|
17
|
+
return !!row && row.value === userId && toTime(row.expiresAt) > now;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* `sessionProven(sessionId, userId)` read from the plugin's record through Better Auth's own adapter, so it agrees
|
|
21
|
+
* with the plugin about where and how the record is stored.
|
|
22
|
+
*/
|
|
23
|
+
export function sessionProofReader(auth, recordPrefix = DEFAULT_ADMIN_PROOF_PREFIX) {
|
|
24
|
+
return async (sessionId, userId) => {
|
|
25
|
+
const context = await auth.$context;
|
|
26
|
+
const row = await context.internalAdapter.findVerificationValue(adminProofIdentifier(recordPrefix, sessionId));
|
|
27
|
+
return isLiveProof(row, userId);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=proof.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proof.js","sourceRoot":"","sources":["../../src/admin/proof.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,yIAAyI;AACzI,MAAM,CAAC,MAAM,0BAA0B,GAAG,sBAAsB,CAAC;AAEjE,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,MAAc,EAAE,SAAiB,EAAE,EAAE,CAAC,GAAG,MAAM,GAAG,SAAS,EAAE,CAAC;AAenG,MAAM,MAAM,GAAG,CAAC,KAAoB,EAAE,EAAE,CAAC,CAAC,KAAK,YAAY,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;AAE/G,sFAAsF;AACtF,MAAM,UAAU,WAAW,CAAC,GAAgC,EAAE,MAAc,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IAC5F,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,KAAK,MAAM,IAAI,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC;AACtE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAoB,EAAE,eAAuB,0BAA0B;IACxG,OAAO,KAAK,EAAE,SAAiB,EAAE,MAAc,EAAoB,EAAE;QACnE,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC;QACpC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,eAAe,CAAC,qBAAqB,CAAC,oBAAoB,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC,CAAC;QAC/G,OAAO,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAClC,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@connextar/house/admin/second-factor` — the Better Auth plugin that records which administrators' sessions were
|
|
3
|
+
* made with a second factor, and refuses an administrator's factor change from a session that wasn't. Its own entry
|
|
4
|
+
* point so `@connextar/house/admin` stays free of Better Auth for an app with its own auth.
|
|
5
|
+
*/
|
|
6
|
+
export { ADMIN_PASSKEY_ADD_PATHS, ADMIN_PROVING_PATHS, ADMIN_SECOND_FACTOR_ERROR_CODES, ADMIN_SECOND_FACTOR_SESSIONS_ID, ADMIN_TWO_FACTOR_CHANGE_PATHS, adminSecondFactorSessions, type AdminSecondFactorSessionsOptions, type AdminSignIn, } from "./second-factor.js";
|
|
7
|
+
export { DEFAULT_ADMIN_PROOF_PREFIX, adminProofIdentifier, sessionProofReader } from "./proof.js";
|
|
8
|
+
//# sourceMappingURL=second-factor-index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"second-factor-index.d.ts","sourceRoot":"","sources":["../../src/admin/second-factor-index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,+BAA+B,EAC/B,+BAA+B,EAC/B,6BAA6B,EAC7B,yBAAyB,EACzB,KAAK,gCAAgC,EACrC,KAAK,WAAW,GACjB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@connextar/house/admin/second-factor` — the Better Auth plugin that records which administrators' sessions were
|
|
3
|
+
* made with a second factor, and refuses an administrator's factor change from a session that wasn't. Its own entry
|
|
4
|
+
* point so `@connextar/house/admin` stays free of Better Auth for an app with its own auth.
|
|
5
|
+
*/
|
|
6
|
+
export { ADMIN_PASSKEY_ADD_PATHS, ADMIN_PROVING_PATHS, ADMIN_SECOND_FACTOR_ERROR_CODES, ADMIN_SECOND_FACTOR_SESSIONS_ID, ADMIN_TWO_FACTOR_CHANGE_PATHS, adminSecondFactorSessions, } from "./second-factor.js";
|
|
7
|
+
export { DEFAULT_ADMIN_PROOF_PREFIX, adminProofIdentifier, sessionProofReader } from "./proof.js";
|
|
8
|
+
//# sourceMappingURL=second-factor-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"second-factor-index.js","sourceRoot":"","sources":["../../src/admin/second-factor-index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,+BAA+B,EAC/B,+BAA+B,EAC/B,6BAA6B,EAC7B,yBAAyB,GAG1B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC"}
|