pi-roundtable-web 0.25.0 → 0.26.1
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/CHANGELOG.md +54 -0
- package/README.md +165 -12
- package/dist/account-zk4efb87.css +102 -0
- package/dist/chunk-account-ghq105t4.js +1 -0
- package/dist/chunk-action-menu-me0g9qsc.js +1 -0
- package/dist/{chunk-agent-0pm9p3nn.js → chunk-agent-rdv7ekrt.js} +1 -1
- package/dist/chunk-arranged-rooms-81rqhx9x.js +1 -0
- package/dist/chunk-character-creator-bd426yjw.js +1 -0
- package/dist/chunk-codeeditor-ajhgd33t.js +2 -0
- package/dist/chunk-confirm-p3mts8mx.js +1 -0
- package/dist/{chunk-connectors-anhghy3k.js → chunk-connectors-bkv3xn83.js} +1 -1
- package/dist/chunk-conversations-0e0vbxwr.js +1 -0
- package/dist/{chunk-drag-q4fx9b22.js → chunk-drag-peyb8t0k.js} +1 -1
- package/dist/{chunk-entry-he1embgp.js → chunk-entry-mv7q8br0.js} +1 -1
- package/dist/chunk-folder-list-era3tksh.js +1 -0
- package/dist/chunk-folds-hvws0bd5.js +1 -0
- package/dist/chunk-gate-hc7dd0fx.js +1 -0
- package/dist/chunk-group-scene-dkbpmbh5.js +1 -0
- package/dist/chunk-group-table-fqmxgdfw.js +1 -0
- package/dist/chunk-host-icons-4xde9ntk.js +1 -0
- package/dist/chunk-host-picture-014yzk20.js +1 -0
- package/dist/chunk-host-state-t56e5sdy.js +1 -0
- package/dist/chunk-host-ui-p1zy9nmp.js +1 -0
- package/dist/chunk-index-0fmmvgee.js +2 -0
- package/dist/chunk-index-0q15ysrb.js +1 -0
- package/dist/chunk-index-0xh24y25.js +2 -0
- package/dist/chunk-index-2yebd9tb.js +426 -0
- package/dist/chunk-index-4t0r3t3b.js +2 -0
- package/dist/chunk-index-5yzfrf7b.js +138 -0
- package/dist/{chunk-index-d5abvq7v.js → chunk-index-6twb7ccd.js} +2 -2
- package/dist/chunk-index-73gh7kgr.js +920 -0
- package/dist/chunk-index-7xffskfa.js +8 -0
- package/dist/chunk-index-aqw7cy5j.js +1 -0
- package/dist/chunk-index-b6ca02e8.js +2 -0
- package/dist/chunk-index-b8nvhcx5.js +2 -0
- package/dist/{chunk-index-bvfqjh77.js → chunk-index-cvbdf2ca.js} +2 -2
- package/dist/chunk-index-czdketze.js +2 -0
- package/dist/{chunk-index-9jqcskb4.js → chunk-index-dt9775ka.js} +5 -5
- package/dist/chunk-index-eg55mqqe.js +1 -0
- package/dist/chunk-index-gr6e0hse.js +2 -0
- package/dist/chunk-index-gyary7np.js +13 -0
- package/dist/chunk-index-j214tczc.js +1 -0
- package/dist/chunk-index-jd6632ef.js +2 -0
- package/dist/chunk-index-jw7dtshr.js +2 -0
- package/dist/{chunk-index-krtk6nn2.js → chunk-index-k7t14rwa.js} +2 -2
- package/dist/chunk-index-kwcx62wb.js +4 -0
- package/dist/chunk-index-m7fvw92f.js +2 -0
- package/dist/chunk-index-mmg788a1.js +2 -0
- package/dist/chunk-index-mq0dacwr.js +2 -0
- package/dist/chunk-index-p53ak25v.js +2 -0
- package/dist/{chunk-index-27qca4mr.js → chunk-index-pk1yxkv4.js} +2 -2
- package/dist/{chunk-index-9b1kt7qw.css → chunk-index-rbcz9jd4.css} +1 -1
- package/dist/{chunk-index-s681nny9.js → chunk-index-rmxbdmn2.js} +2 -2
- package/dist/chunk-index-rnxj3z8k.js +2 -0
- package/dist/chunk-index-snzzfhk3.js +2 -0
- package/dist/chunk-index-v3bmr0nw.js +2 -0
- package/dist/chunk-index-vcyzwtk2.js +2 -0
- package/dist/{chunk-index-zfd00hwb.js → chunk-index-w2j3njb6.js} +2 -2
- package/dist/chunk-index-w31c4yjv.js +2 -0
- package/dist/chunk-index-wrm8w165.js +2 -0
- package/dist/chunk-index-xh0mg9cw.js +2 -0
- package/dist/chunk-index-ybtvvx85.js +2 -0
- package/dist/chunk-index-yzsgsvnp.js +2 -0
- package/dist/{chunk-index-18gxy79n.js → chunk-index-z9x4cnzf.js} +2 -2
- package/dist/chunk-index-zrvg23jd.js +2 -0
- package/dist/chunk-index-zynq0b0w.js +50 -0
- package/dist/chunk-lang-css-88v3hp3d.js +1 -0
- package/dist/{chunk-lang-dockerfile-d5mzxs60.js → chunk-lang-dockerfile-k728ewsf.js} +1 -1
- package/dist/{chunk-lang-go-830fmhba.js → chunk-lang-go-wjwgay4v.js} +3 -3
- package/dist/chunk-lang-html-07d47es4.js +1 -0
- package/dist/chunk-lang-javascript-fmhhgzwd.js +1 -0
- package/dist/{chunk-lang-json-5eaarz7j.js → chunk-lang-json-7gd89mvs.js} +1 -1
- package/dist/chunk-lang-markdown-b6qbe8bt.js +6 -0
- package/dist/{chunk-lang-python-fjw84w10.js → chunk-lang-python-3v56y31b.js} +2 -2
- package/dist/{chunk-lang-rust-5rbp2zdk.js → chunk-lang-rust-kh0z74d1.js} +1 -1
- package/dist/{chunk-lang-shell-vzagpym3.js → chunk-lang-shell-9zv0zvbx.js} +1 -1
- package/dist/{chunk-lang-sql-0c2w9c2y.js → chunk-lang-sql-yw6bf3t7.js} +2 -2
- package/dist/{chunk-lang-toml-yr9fv6eb.js → chunk-lang-toml-tg1r96nq.js} +1 -1
- package/dist/chunk-lang-typescript-etjjvr2g.js +1 -0
- package/dist/{chunk-lang-xml-eze08p42.js → chunk-lang-xml-ev1mndd4.js} +1 -1
- package/dist/{chunk-lang-yaml-c4b6ft3y.js → chunk-lang-yaml-fz5qpb3r.js} +1 -1
- package/dist/chunk-layout-actions-48ecgtkg.js +1 -0
- package/dist/{chunk-menu-items-jrwxycr1.js → chunk-menu-items-1epy3h0t.js} +2 -2
- package/dist/chunk-mode-toggle-fxmfxsa4.js +1 -0
- package/dist/chunk-note-row-kd0fy7ee.js +75 -0
- package/dist/chunk-notes-yb8f057c.js +1 -0
- package/dist/chunk-notice-bell-wr4f1rph.js +1 -0
- package/dist/chunk-notices-x07da5bb.js +1 -0
- package/dist/chunk-onboarding-csrkbh9z.js +1 -0
- package/dist/chunk-overview-avc2d30t.js +1 -0
- package/dist/chunk-palette-9nhp95bz.js +1 -0
- package/dist/chunk-placement-items-q694dpgk.js +1 -0
- package/dist/chunk-profile-6qemf3m7.js +1 -0
- package/dist/chunk-prompt-history-7enzp2p3.js +1 -0
- package/dist/chunk-prompts-nv6mrn0q.js +1 -0
- package/dist/chunk-room-confirms-zz7n61y7.js +1 -0
- package/dist/chunk-room-menu-rr6nzap1.js +1 -0
- package/dist/chunk-rooms-menu-button-zbtwj47d.js +1 -0
- package/dist/chunk-schedules-rw57nr0m.js +1 -0
- package/dist/{chunk-seat-menu-jv4s7hmj.js → chunk-seat-menu-ys5v9abz.js} +1 -1
- package/dist/chunk-sheet-hosts-jhvaacst.js +1 -0
- package/dist/chunk-skill-editor-sz8an8qr.js +8 -0
- package/dist/chunk-skills-tnr1h16h.js +1 -0
- package/dist/chunk-switcher-list-17enhrse.js +1 -0
- package/dist/chunk-tab-dot-r6brdbmw.js +1 -0
- package/dist/chunk-table-r12cja6p.js +1 -0
- package/dist/chunk-table-seats-g3t392zz.js +1 -0
- package/dist/{chunk-table2d-4sje1fr0.js → chunk-table2d-p3ezsdcj.js} +1 -1
- package/dist/{chunk-table3d-h64skazp.js → chunk-table3d-4x2n2b00.js} +23 -23
- package/dist/chunk-thread-actions-qc6ezkha.js +1 -0
- package/dist/chunk-transcript-n2s6dher.js +1 -0
- package/dist/chunk-where-f3scdydc.js +1 -0
- package/dist/chunk-workspace-xs023bxm.js +1 -0
- package/dist/chunk-zh-tw-efvmk3fd.js +1 -0
- package/dist/index.html +1 -1
- package/dist/sw.js +1 -1
- package/package.json +5 -4
- package/src/api-types.ts +356 -4
- package/src/app/app.ts +201 -32
- package/src/app/desk.ts +47 -22
- package/src/app/files.ts +73 -7
- package/src/app/presence.ts +2 -2
- package/src/app/protocol.ts +63 -1
- package/src/app/room-log.ts +153 -14
- package/src/app/rooms.ts +39 -0
- package/src/app/routes.ts +168 -28
- package/src/app/socket.ts +349 -81
- package/src/app/sockets.ts +108 -20
- package/src/app/surface.ts +72 -36
- package/src/app/threads.ts +31 -2
- package/src/app/uploads.ts +33 -7
- package/src/auth/authenticator.ts +77 -8
- package/src/auth/login-page.ts +4 -2
- package/src/auth/next.ts +52 -0
- package/src/auth/oidc-pages.ts +62 -0
- package/src/auth/oidc.ts +649 -0
- package/src/auth/sessions.ts +193 -0
- package/src/authz.ts +173 -0
- package/src/console-api.ts +122 -25
- package/src/console-server.ts +102 -12
- package/src/gate.ts +90 -0
- package/src/host/access.ts +161 -0
- package/src/host/cards.ts +112 -0
- package/src/host/context.ts +188 -0
- package/src/host/forms.ts +240 -0
- package/src/host/frames.ts +349 -0
- package/src/host/host-app.ts +178 -0
- package/src/host/host-context.ts +49 -0
- package/src/host/host-routes.ts +396 -0
- package/src/host/pages.ts +266 -0
- package/src/host/reads.ts +155 -0
- package/src/host/retention.ts +41 -0
- package/src/host/room-views.ts +137 -0
- package/src/host/rooms-service.ts +429 -0
- package/src/host/stored-json.ts +13 -0
- package/src/host/views.ts +106 -0
- package/src/i18n/zh-tw.ts +139 -0
- package/src/index.ts +15 -1
- package/src/notices/api.ts +43 -0
- package/src/notices/mount.ts +292 -0
- package/src/notices/service.ts +294 -0
- package/src/notices/store.ts +298 -0
- package/src/notices/urls.ts +38 -0
- package/src/onboarding/api.ts +32 -0
- package/src/onboarding/service.ts +149 -0
- package/src/onboarding/steps.ts +75 -0
- package/src/onboarding/waivers.ts +40 -0
- package/src/options.ts +534 -34
- package/src/page-presentation.ts +47 -2
- package/src/profile/api.ts +175 -0
- package/src/profile/links.ts +162 -0
- package/src/push/api.ts +189 -52
- package/src/push/dispatch.ts +156 -0
- package/src/push/drain.ts +139 -0
- package/src/push/outbox.ts +218 -0
- package/src/push/prefs.ts +118 -0
- package/src/push/sender.ts +7 -2
- package/src/push/triggers.ts +56 -5
- package/src/push/vapid.ts +19 -0
- package/src/push/web-push-transport.ts +106 -0
- package/src/push-sender/index.ts +14 -0
- package/src/push-sender/run.ts +118 -0
- package/src/push-sender/session.ts +54 -0
- package/src/pwa.ts +32 -4
- package/src/visitors.ts +94 -6
- package/src/web-app.ts +718 -0
- package/src/web-plugin.ts +279 -26
- package/dist/chunk-action-menu-qe839g4b.js +0 -1
- package/dist/chunk-arranged-rooms-enpfswmg.js +0 -1
- package/dist/chunk-character-creator-6yf29xwx.js +0 -1
- package/dist/chunk-codeeditor-gdbdg7rp.js +0 -2
- package/dist/chunk-confirm-xrw7d732.js +0 -1
- package/dist/chunk-conversations-ry228aes.js +0 -1
- package/dist/chunk-folder-list-5cp8stna.js +0 -1
- package/dist/chunk-folds-hff104tk.js +0 -1
- package/dist/chunk-group-scene-h228jdat.js +0 -1
- package/dist/chunk-index-0p5h2kdz.js +0 -2
- package/dist/chunk-index-0pt08yd9.js +0 -1
- package/dist/chunk-index-12bw9yj8.js +0 -2
- package/dist/chunk-index-2ad17eh4.js +0 -2
- package/dist/chunk-index-31wh5twj.js +0 -52
- package/dist/chunk-index-4dy94pbz.js +0 -2
- package/dist/chunk-index-4ecjtdy8.js +0 -13
- package/dist/chunk-index-4v1b7ks9.js +0 -2
- package/dist/chunk-index-5d8yjj4m.js +0 -2
- package/dist/chunk-index-89ajmxh7.js +0 -2
- package/dist/chunk-index-b9ny4623.js +0 -2
- package/dist/chunk-index-e1dr6g10.js +0 -2
- package/dist/chunk-index-gpkzhq1g.js +0 -2
- package/dist/chunk-index-hk3xyy20.js +0 -2
- package/dist/chunk-index-jm23vn03.js +0 -4
- package/dist/chunk-index-k7gh0jvz.js +0 -2
- package/dist/chunk-index-ky42ev0b.js +0 -2
- package/dist/chunk-index-pa2yyqh0.js +0 -2
- package/dist/chunk-index-pcvt668q.js +0 -2
- package/dist/chunk-index-pqw27eye.js +0 -1
- package/dist/chunk-index-wmbmmrd4.js +0 -302
- package/dist/chunk-index-ymbjrsp6.js +0 -2
- package/dist/chunk-lang-css-t85r8rz0.js +0 -1
- package/dist/chunk-lang-html-n6jparge.js +0 -1
- package/dist/chunk-lang-javascript-xj00cp97.js +0 -1
- package/dist/chunk-lang-markdown-zxx8eke8.js +0 -6
- package/dist/chunk-lang-typescript-8f6yywt3.js +0 -1
- package/dist/chunk-layout-actions-bv0chj41.js +0 -1
- package/dist/chunk-notes-9b7pgqfe.js +0 -1
- package/dist/chunk-overview-8yw71esz.js +0 -1
- package/dist/chunk-palette-frms7axe.js +0 -1
- package/dist/chunk-placement-items-fvfy9m0y.js +0 -1
- package/dist/chunk-prompt-history-yrqehads.js +0 -1
- package/dist/chunk-prompts-g6609tqx.js +0 -1
- package/dist/chunk-room-confirms-0a2y0925.js +0 -1
- package/dist/chunk-room-menu-g11q97br.js +0 -1
- package/dist/chunk-rooms-menu-button-h9qge48n.js +0 -1
- package/dist/chunk-schedules-ezmacrbj.js +0 -1
- package/dist/chunk-sheet-hosts-b0azyfhb.js +0 -1
- package/dist/chunk-skill-editor-h6k6whwg.js +0 -8
- package/dist/chunk-skills-gqcfhq56.js +0 -1
- package/dist/chunk-switcher-list-mcpamqnj.js +0 -1
- package/dist/chunk-table-s8cde1xz.js +0 -1
- package/dist/chunk-table-seats-wqkjn8bc.js +0 -1
- package/dist/chunk-transcript-c59c6mpm.js +0 -1
- package/dist/chunk-workspace-0cyspkk4.js +0 -1
- package/dist/chunk-zh-tw-nc93jm10.js +0 -1
|
@@ -4,8 +4,10 @@ import type { ResolvedAuth } from "../options.ts";
|
|
|
4
4
|
import type { Verdict } from "../verifier.ts";
|
|
5
5
|
import type { Identified, Visitor } from "../visitors.ts";
|
|
6
6
|
import { checkNoneListener } from "./none.ts";
|
|
7
|
+
import type { OidcLogin } from "./oidc.ts";
|
|
7
8
|
import { checkPassword, PasswordCredential } from "./password.ts";
|
|
8
9
|
import { loadSessionKey } from "./session-key.ts";
|
|
10
|
+
import type { WebSessions } from "./sessions.ts";
|
|
9
11
|
|
|
10
12
|
/** The plugin identity `none` and `password` speak through; `RoundtablePlugin.identities` links it to the owner. */
|
|
11
13
|
export const WEB_CONSOLE_IDENTITY = "token:web-console";
|
|
@@ -22,6 +24,14 @@ export interface Authenticated {
|
|
|
22
24
|
visitor: Visitor;
|
|
23
25
|
actor?: ActorFacts;
|
|
24
26
|
expiresAt?: Date;
|
|
27
|
+
/** The session the credential is, when it is one: what an open socket asks about again. */
|
|
28
|
+
session?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** What a socket holds of the credential it came in on, to ask again whether it still stands. */
|
|
32
|
+
export interface Held {
|
|
33
|
+
actor: ActorFacts | undefined;
|
|
34
|
+
session?: string | undefined;
|
|
25
35
|
}
|
|
26
36
|
|
|
27
37
|
/** A request that may not, with the status that says so; `login` when signing in would help. */
|
|
@@ -38,6 +48,12 @@ export interface Unauthenticated {
|
|
|
38
48
|
*/
|
|
39
49
|
export interface Authenticator {
|
|
40
50
|
authenticate(request: Request): Promise<Authenticated | Unauthenticated>;
|
|
51
|
+
/**
|
|
52
|
+
* Asks again about a credential already admitted, as an open socket does for every frame and on
|
|
53
|
+
* a timer: does the session still stand, and who is the person now, at which tier. It reads the
|
|
54
|
+
* same `IDENTITY` as a request does, so a person disabled or demoted since is seen at once.
|
|
55
|
+
*/
|
|
56
|
+
recheck(held: Held): Promise<{ visitor: Visitor } | Unauthenticated>;
|
|
41
57
|
/**
|
|
42
58
|
* Answers the paths below the mount that need no session, such as the login page; undefined
|
|
43
59
|
* for any other, and absent for a mode with none.
|
|
@@ -60,6 +76,11 @@ export interface AuthenticatorOptions {
|
|
|
60
76
|
dataDir: string;
|
|
61
77
|
/** The listener the console is served on and its address, for the check of `none`. */
|
|
62
78
|
listener: { id: string; address: Readonly<ListenerAddress> | undefined };
|
|
79
|
+
/** The sign-in flow and the session store, for `oidc`. */
|
|
80
|
+
oidc?: {
|
|
81
|
+
login: Pick<OidcLogin, "open" | "credential" | "verified">;
|
|
82
|
+
sessions: Pick<WebSessions, "alive">;
|
|
83
|
+
};
|
|
63
84
|
/** Where the password is read from; default `process.env`. */
|
|
64
85
|
env?: Readonly<Record<string, string | undefined>>;
|
|
65
86
|
/** How long each failed login waits; default 1000 ms. Tests set it lower. */
|
|
@@ -68,8 +89,15 @@ export interface AuthenticatorOptions {
|
|
|
68
89
|
now?: () => number;
|
|
69
90
|
}
|
|
70
91
|
|
|
92
|
+
/** A credential that admits: who it vouches for, and the session it is when it is one. */
|
|
93
|
+
export type Admitted = Extract<Verdict, { admitted: true }> & {
|
|
94
|
+
session?: string;
|
|
95
|
+
};
|
|
96
|
+
|
|
71
97
|
/** What a credential says about a request, before the person is identified. */
|
|
72
|
-
type Credential = (
|
|
98
|
+
export type Credential = (
|
|
99
|
+
request: Request,
|
|
100
|
+
) => Promise<Verdict | Admitted | Unauthenticated>;
|
|
73
101
|
|
|
74
102
|
const READ_METHODS = new Set(["GET", "HEAD"]);
|
|
75
103
|
|
|
@@ -78,6 +106,16 @@ const denied = (refusal: string): Unauthenticated => ({
|
|
|
78
106
|
status: 403,
|
|
79
107
|
});
|
|
80
108
|
|
|
109
|
+
/** A refusal in the shape identification answers with. */
|
|
110
|
+
const refused = (reason: string): Identified => ({ refusal: reason });
|
|
111
|
+
|
|
112
|
+
/** A session that is gone, which signing in again would fix. */
|
|
113
|
+
const signedOut = (reason: string): Unauthenticated => ({
|
|
114
|
+
refusal: reason,
|
|
115
|
+
status: 401,
|
|
116
|
+
login: true,
|
|
117
|
+
});
|
|
118
|
+
|
|
81
119
|
/** Admits as the verifier says; one that throws or rejects admits no one. */
|
|
82
120
|
function verifierCredential(
|
|
83
121
|
verifier: (request: Request) => Verdict | Promise<Verdict>,
|
|
@@ -112,6 +150,11 @@ export function createAuthenticator(
|
|
|
112
150
|
logger,
|
|
113
151
|
);
|
|
114
152
|
credential = async () => ({ admitted: true, actor: WEB_CONSOLE_ACTOR });
|
|
153
|
+
} else if (auth.mode === "oidc") {
|
|
154
|
+
if (!options.oidc) throw new Error("the oidc sign-in is not set up");
|
|
155
|
+
credential = options.oidc.login.credential;
|
|
156
|
+
open = (request, rest) =>
|
|
157
|
+
options.oidc?.login.open(request, rest) ?? Promise.resolve(undefined);
|
|
115
158
|
} else {
|
|
116
159
|
const password = new PasswordCredential({
|
|
117
160
|
password: checkPassword(options.env ?? process.env, auth.passwordEnv),
|
|
@@ -138,30 +181,56 @@ export function createAuthenticator(
|
|
|
138
181
|
};
|
|
139
182
|
open = (request, rest) => password.open(request, rest);
|
|
140
183
|
}
|
|
184
|
+
const identify = async (
|
|
185
|
+
actor: ActorFacts | undefined,
|
|
186
|
+
): Promise<Identified> => {
|
|
187
|
+
try {
|
|
188
|
+
return await options.identify(actor);
|
|
189
|
+
} catch {
|
|
190
|
+
// A lookup of who they are that fails admits no one.
|
|
191
|
+
return refused("identity failed");
|
|
192
|
+
}
|
|
193
|
+
};
|
|
141
194
|
return {
|
|
142
195
|
...(open ? { open } : {}),
|
|
143
196
|
async authenticate(request) {
|
|
144
197
|
const checked = await credential(request);
|
|
145
198
|
if ("refusal" in checked) return checked;
|
|
146
199
|
if (!checked.admitted) return denied(String(checked.reason));
|
|
147
|
-
|
|
148
|
-
try {
|
|
149
|
-
identified = await options.identify(checked.actor);
|
|
150
|
-
} catch {
|
|
151
|
-
// A lookup of who they are that fails admits no one.
|
|
152
|
-
return denied("identity failed");
|
|
153
|
-
}
|
|
200
|
+
const identified = await identify(checked.actor);
|
|
154
201
|
if ("refusal" in identified) return denied(String(identified.refusal));
|
|
155
202
|
if (
|
|
156
203
|
!READ_METHODS.has(request.method) &&
|
|
157
204
|
request.headers.get("origin") !== options.origin
|
|
158
205
|
)
|
|
159
206
|
return denied("foreign origin");
|
|
207
|
+
const { session } = checked as Admitted;
|
|
208
|
+
if (session)
|
|
209
|
+
options.oidc?.login.verified(session, identified.visitor.principal.id);
|
|
160
210
|
return {
|
|
161
211
|
visitor: identified.visitor,
|
|
162
212
|
...(checked.actor ? { actor: checked.actor } : {}),
|
|
163
213
|
...(checked.expiresAt ? { expiresAt: checked.expiresAt } : {}),
|
|
214
|
+
...(session ? { session } : {}),
|
|
164
215
|
};
|
|
165
216
|
},
|
|
217
|
+
async recheck(held) {
|
|
218
|
+
if (held.session) {
|
|
219
|
+
try {
|
|
220
|
+
if (!(await options.oidc?.sessions.alive(held.session)))
|
|
221
|
+
return signedOut("session ended");
|
|
222
|
+
} catch {
|
|
223
|
+
return denied("session lookup failed");
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const identified = await identify(held.actor);
|
|
227
|
+
if ("refusal" in identified) return denied(String(identified.refusal));
|
|
228
|
+
if (held.session)
|
|
229
|
+
options.oidc?.login.verified(
|
|
230
|
+
held.session,
|
|
231
|
+
identified.visitor.principal.id,
|
|
232
|
+
);
|
|
233
|
+
return { visitor: identified.visitor };
|
|
234
|
+
},
|
|
166
235
|
};
|
|
167
236
|
}
|
package/src/auth/login-page.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type ConsolePresentation, message } from "../features.ts";
|
|
2
2
|
import { escapeHtml } from "../http.ts";
|
|
3
|
+
import { iconLinks } from "../page-presentation.ts";
|
|
3
4
|
|
|
4
|
-
/** The login page's CSP: no script at all, one inline style, and the form posts only to this origin. */
|
|
5
|
+
/** The login page's CSP: no script at all, one inline style, its own icon, and the form posts only to this origin. */
|
|
5
6
|
export const LOGIN_CSP =
|
|
6
|
-
"default-src 'none'; style-src 'unsafe-inline'; form-action 'self'; base-uri 'none'; frame-ancestors 'none'";
|
|
7
|
+
"default-src 'none'; style-src 'unsafe-inline'; img-src 'self'; form-action 'self'; base-uri 'none'; frame-ancestors 'none'";
|
|
7
8
|
|
|
8
9
|
/** The login page, as one server-rendered document with no script; `note` is a line above the field. */
|
|
9
10
|
export function loginPage(
|
|
@@ -19,6 +20,7 @@ export function loginPage(
|
|
|
19
20
|
<meta charset="utf-8">
|
|
20
21
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
21
22
|
<meta name="robots" content="noindex">
|
|
23
|
+
${iconLinks(mount, false)}
|
|
22
24
|
<title>${escapeHtml(title)}</title>
|
|
23
25
|
<style>
|
|
24
26
|
body{font:16px/1.5 system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#f6f6f4;color:#1c1c1a}
|
package/src/auth/next.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a sign-in goes back to. A page link (from the host's own dashboard, a notification, or a
|
|
3
|
+
* page the server turned away) names it as `?next=`, and only these shapes are taken: anything
|
|
4
|
+
* else is dropped, so the callback never redirects to a place a link chose.
|
|
5
|
+
*
|
|
6
|
+
* - `profile`, `notices`, `settings`: a page of the app;
|
|
7
|
+
* - `room:<key>`: a room of this app's surface (`<surface>:<kind>.<id>`);
|
|
8
|
+
* - `p:<id>`: a host page, `[a-z0-9-]{1,40}`.
|
|
9
|
+
*/
|
|
10
|
+
export function allowedNext(
|
|
11
|
+
value: string | null | undefined,
|
|
12
|
+
surface: string,
|
|
13
|
+
): string | undefined {
|
|
14
|
+
if (!value) return undefined;
|
|
15
|
+
if (value === "profile" || value === "notices" || value === "settings")
|
|
16
|
+
return value;
|
|
17
|
+
const room = /^room:(.+)$/.exec(value)?.[1];
|
|
18
|
+
if (room !== undefined)
|
|
19
|
+
return new RegExp(`^${surface}:[A-Za-z0-9._~-]{1,160}$`).test(room)
|
|
20
|
+
? value
|
|
21
|
+
: undefined;
|
|
22
|
+
const page = /^p:(.+)$/.exec(value)?.[1];
|
|
23
|
+
return page !== undefined && /^[a-z0-9-]{1,40}$/.test(page)
|
|
24
|
+
? value
|
|
25
|
+
: undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The app route a `next` names, as the page's router writes it: `room/<key>`, `p/<id>` or the page's name. */
|
|
29
|
+
export function routeOfNext(next: string): string {
|
|
30
|
+
if (next.startsWith("room:"))
|
|
31
|
+
return `room/${encodeURIComponent(next.slice("room:".length))}`;
|
|
32
|
+
if (next.startsWith("p:")) return `p/${next.slice("p:".length)}`;
|
|
33
|
+
return next;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The `next` a page path stands for, for a server that turns away a page navigation (path
|
|
38
|
+
* routing only: a hash route never reaches the server). Undefined for a path that names none.
|
|
39
|
+
*/
|
|
40
|
+
export function nextOfPath(rest: string, surface: string): string | undefined {
|
|
41
|
+
const [first, second, ...more] = rest.split("/").filter(Boolean);
|
|
42
|
+
if (more.length > 0 || first === undefined) return undefined;
|
|
43
|
+
try {
|
|
44
|
+
if (second === undefined) return allowedNext(first, surface);
|
|
45
|
+
const key = decodeURIComponent(second);
|
|
46
|
+
if (first === "room") return allowedNext(`room:${key}`, surface);
|
|
47
|
+
if (first === "p") return allowedNext(`p:${key}`, surface);
|
|
48
|
+
} catch {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type ConsolePresentation, message } from "../features.ts";
|
|
2
|
+
import { escapeHtml } from "../http.ts";
|
|
3
|
+
import { iconLinks } from "../page-presentation.ts";
|
|
4
|
+
import { LOGIN_CSP } from "./login-page.ts";
|
|
5
|
+
|
|
6
|
+
/** What a page of the sign-in flow tells, and the link it offers. */
|
|
7
|
+
type Notice = "signed-out" | "failed" | "not-allowed";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The static pages of the sign-in flow: logged out, a sign-in that did not complete, and a person
|
|
11
|
+
* the app does not admit. They carry no script and no detail of why: the reason goes to the log.
|
|
12
|
+
*/
|
|
13
|
+
export function oidcPage(
|
|
14
|
+
notice: Notice,
|
|
15
|
+
mount: string,
|
|
16
|
+
title: string,
|
|
17
|
+
presentation: ConsolePresentation | undefined,
|
|
18
|
+
status: number,
|
|
19
|
+
): Response {
|
|
20
|
+
const say = (source: string) => escapeHtml(message(presentation, source));
|
|
21
|
+
const text =
|
|
22
|
+
notice === "signed-out"
|
|
23
|
+
? say("You are signed out.")
|
|
24
|
+
: notice === "not-allowed"
|
|
25
|
+
? say("This account is not allowed to use this app.")
|
|
26
|
+
: say("Sign-in did not complete.");
|
|
27
|
+
const link = notice === "signed-out" ? say("Sign in") : say("Try again");
|
|
28
|
+
const html = `<!doctype html>
|
|
29
|
+
<html lang="${escapeHtml(presentation?.locale ?? "en")}">
|
|
30
|
+
<head>
|
|
31
|
+
<meta charset="utf-8">
|
|
32
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
33
|
+
<meta name="robots" content="noindex">
|
|
34
|
+
${iconLinks(mount, false)}
|
|
35
|
+
<title>${escapeHtml(title)}</title>
|
|
36
|
+
<style>
|
|
37
|
+
body{font:16px/1.5 system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#f6f6f4;color:#1c1c1a}
|
|
38
|
+
@media(prefers-color-scheme:dark){body{background:#151514;color:#ecece8}}
|
|
39
|
+
main{width:min(22rem,calc(100vw - 2rem));display:grid;gap:1rem}
|
|
40
|
+
h1{font-size:1.25rem;margin:0}
|
|
41
|
+
p{margin:0}
|
|
42
|
+
a{font:inherit;padding:.6rem .75rem;border-radius:.5rem;background:#2d5be3;color:#fff;text-decoration:none;text-align:center}
|
|
43
|
+
</style>
|
|
44
|
+
</head>
|
|
45
|
+
<body>
|
|
46
|
+
<main>
|
|
47
|
+
<h1>${escapeHtml(title)}</h1>
|
|
48
|
+
<p role="${notice === "signed-out" ? "status" : "alert"}">${text}</p>
|
|
49
|
+
<a href="${escapeHtml(mount)}/login">${link}</a>
|
|
50
|
+
</main>
|
|
51
|
+
</body>
|
|
52
|
+
</html>
|
|
53
|
+
`;
|
|
54
|
+
return new Response(html, {
|
|
55
|
+
status,
|
|
56
|
+
headers: {
|
|
57
|
+
"content-type": "text/html;charset=utf-8",
|
|
58
|
+
"cache-control": "no-store",
|
|
59
|
+
"content-security-policy": LOGIN_CSP,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|