@tiddlywiki/mws 0.0.12 → 0.0.13

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.
Files changed (65) hide show
  1. package/README.md +3 -1
  2. package/dist/server.d.ts +923 -0
  3. package/dist/server.js +1763 -1415
  4. package/dist/server.js.map +1 -1
  5. package/editions/mws-docs/tiddlers/$__StoryList.tid +3 -3
  6. package/editions/mws-docs/tiddlers/MWS and SQLite.md +11 -0
  7. package/editions/mws-docs/tiddlers/Request Server.md +20 -0
  8. package/editions/mws-docs/tiddlers/Request Server.md.meta +5 -0
  9. package/package.json +16 -10
  10. package/prisma/schema.prisma +1 -0
  11. package/react-user-mgmt/package-lock.json +76 -321
  12. package/react-user-mgmt/package.json +2 -5
  13. package/react-user-mgmt/public/chunk-YALWN5OT.js +2 -0
  14. package/react-user-mgmt/public/chunk-YALWN5OT.js.map +7 -0
  15. package/react-user-mgmt/public/esm-QKDFEP3B.js +3 -0
  16. package/react-user-mgmt/public/esm-QKDFEP3B.js.map +7 -0
  17. package/react-user-mgmt/public/index.html +1 -26
  18. package/react-user-mgmt/public/main.js +68 -109
  19. package/react-user-mgmt/public/main.js.map +4 -4
  20. package/react-user-mgmt/public/stats.css +597 -0
  21. package/react-user-mgmt/public/stats.html +59 -0
  22. package/react-user-mgmt/public/stats.js +2013 -0
  23. package/react-user-mgmt/public/stats.json +1 -0
  24. package/react-user-mgmt/public/stats.svg +4 -0
  25. package/react-user-mgmt/tsconfig.json +3 -0
  26. package/dist/server.cjs +0 -16677
  27. package/dist/server.cjs.map +0 -1
  28. package/src/StateObject.ts +0 -316
  29. package/src/commander.ts +0 -417
  30. package/src/commands/divider.ts +0 -20
  31. package/src/commands/index.ts +0 -24
  32. package/src/commands/mws-init-store.ts +0 -77
  33. package/src/commands/mws-listen.ts +0 -136
  34. package/src/commands/mws-load-archive.ts +0 -281
  35. package/src/commands/mws-load-plugin-bags.ts +0 -151
  36. package/src/commands/mws-load-tiddlers.ts +0 -37
  37. package/src/commands/mws-load-wiki-folder.ts +0 -124
  38. package/src/commands/mws-render-tiddlywiki5.ts +0 -55
  39. package/src/commands/mws-save-archive.ts +0 -111
  40. package/src/commands/mws-save-tiddler-text.ts +0 -33
  41. package/src/commands/tests-complete.ts +0 -21
  42. package/src/global.ts +0 -247
  43. package/src/routes/TiddlerStore.ts +0 -722
  44. package/src/routes/bag-file-server.ts +0 -340
  45. package/src/routes/cache.ts +0 -25
  46. package/src/routes/index.ts +0 -138
  47. package/src/routes/managers/index.ts +0 -17
  48. package/src/routes/managers/manager-recipes.ts +0 -438
  49. package/src/routes/managers/manager-users.ts +0 -201
  50. package/src/routes/managers/router-tiddlers.ts +0 -313
  51. package/src/routes/router.ts +0 -429
  52. package/src/routes/services/PasswordService.ts +0 -178
  53. package/src/routes/services/attachments.ts +0 -259
  54. package/src/routes/services/sessions.ts +0 -166
  55. package/src/routes/tw-test.ts +0 -65
  56. package/src/server.ts +0 -151
  57. package/src/setupDevServer.ts +0 -71
  58. package/src/streamer.ts +0 -430
  59. package/src/tiddlywiki.ts +0 -94
  60. package/src/utils/data-checks.ts +0 -172
  61. package/src/utils/index.ts +0 -4
  62. package/src/utils/rootRoute.ts +0 -237
  63. package/src/utils/utils.ts +0 -403
  64. package/src/utils/zodAssert.ts +0 -269
  65. /package/prisma/{migrations/notes.md → notes.md} +0 -0
@@ -1,429 +0,0 @@
1
- import { STREAM_ENDED, Streamer } from "../streamer";
2
- import { StateObject } from "../StateObject";
3
- import RootRoute from ".";
4
- import * as z from "zod";
5
- import { createStrictAwaitProxy, JsonValue, truthy, Z2 } from "../utils";
6
- import { Route, rootRoute, RouteOptAny, RouteMatch, } from "../utils";
7
- import { MWSConfigConfig } from "../server";
8
- import { setupDevServer } from "../setupDevServer";
9
- import { Commander } from "../commander";
10
- import { CacheState, startupCache } from "./cache";
11
-
12
- export { RouteMatch, Route, rootRoute };
13
-
14
- export const AllowedMethods = [...["GET", "HEAD", "OPTIONS", "POST", "PUT", "DELETE"] as const];
15
- export type AllowedMethod = typeof AllowedMethods[number];
16
-
17
- export const BodyFormats = ["stream", "string", "json", "buffer", "www-form-urlencoded", "www-form-urlencoded-urlsearchparams", "ignore"] as const;
18
- export type BodyFormat = typeof BodyFormats[number];
19
-
20
- export const PermissionName = []
21
-
22
- const zodTransformJSON = (arg: string, ctx: z.RefinementCtx) => {
23
- try {
24
- if (arg === "") return undefined;
25
- return JSON.parse(arg, (key, value) => {
26
- //https://github.com/fastify/secure-json-parse
27
- if (key === '__proto__')
28
- throw new Error('Invalid key: __proto__');
29
- if (key === 'constructor' && Object.prototype.hasOwnProperty.call(value, 'prototype'))
30
- throw new Error('Invalid key: constructor.prototype');
31
- return value;
32
- });
33
- } catch (e) {
34
- ctx.addIssue({
35
- code: z.ZodIssueCode.custom,
36
- message: e instanceof Error ? e.message : `${e}`,
37
- fatal: true,
38
- });
39
- return z.NEVER;
40
- }
41
- };
42
-
43
- export interface SiteConfig extends MWSConfigConfig {
44
- wikiPath: string;
45
- attachmentSizeLimit: number;
46
- attachmentsEnabled: boolean;
47
- contentTypeInfo: Record<string, any>;
48
- saveLargeTextToFileSystem: never;
49
- storePath: string;
50
- }
51
-
52
- export class Router {
53
-
54
- static async makeRouter(
55
- commander: Commander,
56
- enableDevServer: string | undefined
57
- ) {
58
-
59
- const sendDevServer = await setupDevServer(enableDevServer);
60
-
61
- const rootRoute = defineRoute(ROOT_ROUTE, {
62
- method: AllowedMethods,
63
- path: /^/,
64
- denyFinal: true,
65
- }, async (state: StateObject) => {
66
- state.sendDevServer = sendDevServer.bind(undefined, state);
67
- });
68
-
69
- await commander.SessionManager.defineRoutes(rootRoute);
70
-
71
- await RootRoute(rootRoute, commander.siteConfig);
72
-
73
- const cache = await startupCache(commander);
74
-
75
- return new Router(rootRoute, commander, cache);
76
- }
77
-
78
-
79
- pathPrefix: string = "";
80
- enableBrowserCache: boolean = true;
81
- enableGzip: boolean = false;
82
- csrfDisable: boolean = false;
83
- servername: string = "";
84
- variables = new Map();
85
- get(name: string): string {
86
- return this.variables.get(name) || "";
87
- }
88
- public engine: Commander["engine"];
89
- private SessionManager: Commander["SessionManager"];
90
-
91
- constructor(
92
- private rootRoute: rootRoute,
93
- private commander: Commander,
94
- private tiddlerCache: CacheState,
95
- ) {
96
- this.engine = commander.engine;
97
- this.SessionManager = commander.SessionManager;
98
-
99
- }
100
-
101
- async handle(streamer: Streamer) {
102
-
103
- if (!this.csrfDisable
104
- && ["POST", "PUT", "DELETE"].includes(streamer.method)
105
- && streamer.headers["x-requested-with"] !== "TiddlyWiki"
106
- )
107
- throw streamer.sendString(403, { "x-reason": "x-requested-with missing" },
108
- `'X-Requested-With' header required to login to '${this.servername}'`, "utf8");
109
-
110
- const authUser = await this.SessionManager.parseIncomingRequest(streamer, this);
111
-
112
- /** This should always have a length of at least 1 because of the root route. */
113
- const routePath = this.findRoute(streamer);
114
- if (!routePath.length || routePath[routePath.length - 1]?.route.denyFinal)
115
- return streamer.sendEmpty(404, { "x-reason": "no-route" });
116
-
117
- // Optionally output debug info
118
- console.log(streamer.method, streamer.url);
119
- // if no bodyFormat is specified, we default to "buffer" since we do still need to recieve the body
120
- const bodyFormat = routePath.find(e => e.route.bodyFormat)?.route.bodyFormat || "buffer";
121
-
122
- type statetype = { [K in BodyFormat]: StateObject<K> }[BodyFormat]
123
-
124
- const state = createStrictAwaitProxy(
125
- new StateObject(
126
- streamer,
127
- routePath,
128
- bodyFormat,
129
- authUser,
130
- this.commander,
131
- this.tiddlerCache,
132
- ) as statetype
133
- );
134
-
135
-
136
- const method = streamer.method;
137
-
138
- // anything that sends a response before this should have thrown, but just in case
139
- if (streamer.headersSent) return;
140
-
141
- if (["GET", "HEAD"].includes(method)) state.bodyFormat = "ignore";
142
-
143
- if (state.bodyFormat === "stream" || state.bodyFormat === "ignore") {
144
- // this starts dumping bytes early, rather than letting node do it once the res finishes.
145
- // the only advantage is that it eases congestion on the socket.
146
- if (state.bodyFormat === "ignore") streamer.reader.resume();
147
-
148
- return await this.handleRoute(state, routePath);
149
- }
150
- if (state.bodyFormat === "string" || state.bodyFormat === "json") {
151
- state.data = (await state.readBody()).toString("utf8");
152
- if (state.bodyFormat === "json") {
153
- // make sure this parses as valid data
154
- const { success, data } = z.string().transform(zodTransformJSON).safeParse(state.data);
155
- if (!success) return state.sendEmpty(400, {});
156
- state.data = data;
157
- }
158
- } else if (state.bodyFormat === "www-form-urlencoded-urlsearchparams"
159
- || state.bodyFormat === "www-form-urlencoded") {
160
- const data = state.data = new URLSearchParams((await state.readBody()).toString("utf8"));
161
- if (state.bodyFormat === "www-form-urlencoded") {
162
- state.data = Object.fromEntries(data);
163
- }
164
- } else if (state.bodyFormat === "buffer") {
165
- state.data = await state.readBody();
166
- } else {
167
- // because it's a union, state becomes never at this point if we matched every route correctly
168
- // make sure state is never by assigning it to a never const. This will error if something is missed.
169
- const t: never = state;
170
- const state2: StateObject = state as any;
171
- return state2.sendString(500, {}, "Invalid bodyFormat: " + state2.bodyFormat, "utf8");
172
- }
173
-
174
- return await this.handleRoute(state, routePath);
175
-
176
- }
177
-
178
-
179
-
180
- async handleRoute(state: StateObject<BodyFormat>, route: RouteMatch[]) {
181
-
182
- let result: any = state;
183
- for (const match of route) {
184
- await match.route.handler(result);
185
- if (state.headersSent) return;
186
- }
187
- if (!state.headersSent) {
188
- state.sendEmpty(404, {});
189
- console.log("No handler sent headers before the promise resolved.");
190
- }
191
-
192
- }
193
-
194
- findRouteRecursive(
195
- routes: Route[],
196
- testPath: string,
197
- method: AllowedMethod
198
- ): RouteMatch[] {
199
- for (const potentialRoute of routes) {
200
- // Skip if the method doesn't match.
201
- if (!potentialRoute.method.includes(method)) continue;
202
-
203
- // Try to match the path.
204
- const match = potentialRoute.path.exec(testPath);
205
-
206
- if (match) {
207
- // The matched portion of the path.
208
- const matchedPortion = match[0];
209
- // Remove the matched portion from the testPath.
210
- const remainingPath = testPath.slice(matchedPortion.length) || "/";
211
-
212
- const result = {
213
- route: potentialRoute,
214
- params: match.slice(1),
215
- remainingPath,
216
- };
217
- const { childRoutes = [] } = potentialRoute as any; // see this.defineRoute
218
- // If there are inner routes, try to match them recursively.
219
- if (childRoutes.length > 0) {
220
- const innerMatch = this.findRouteRecursive(
221
- childRoutes,
222
- remainingPath,
223
- method
224
- );
225
- return [result, ...innerMatch];
226
- } else {
227
- return [result];
228
- }
229
- }
230
- }
231
- return [];
232
- }
233
-
234
- /**
235
- *
236
- * Top-level function that starts matching from the root routes.
237
- * Notice that the pathPrefix is assumed to have been handled beforehand.
238
- *
239
- * @param streamer
240
- * @returns The tree path matched
241
- */
242
- findRoute(streamer: Streamer): RouteMatch[] {
243
- const { method, urlInfo } = streamer;
244
- let testPath = urlInfo.pathname || "/";
245
- if (this.pathPrefix && testPath.startsWith(this.pathPrefix))
246
- testPath = testPath.slice(this.pathPrefix.length) || "/";
247
- return this.findRouteRecursive([this.rootRoute as any], testPath, method);
248
- }
249
-
250
- }
251
-
252
- const ROOT_ROUTE: unique symbol = Symbol("ROOT_ROUTE");
253
- function defineRoute(
254
- parent: { $o?: any, method: any } | typeof ROOT_ROUTE,
255
- route: RouteOptAny,
256
- handler: (state: any) => any,
257
- ) {
258
-
259
- if (route.bodyFormat && !BodyFormats.includes(route.bodyFormat))
260
- throw new Error("Invalid bodyFormat: " + route.bodyFormat);
261
- if (!route.method.every(e => (parent === ROOT_ROUTE ? AllowedMethods : parent.method).includes(e)))
262
- throw new Error("Invalid method: " + route.method);
263
- if (route.path.source[0] !== "^")
264
- throw new Error("Path regex must start with ^");
265
-
266
- if (parent !== ROOT_ROUTE) {
267
- // the typing is too complicated if we add childRoutes
268
- if (!(parent as any).childRoutes) (parent as any).childRoutes = [];
269
- (parent as any).childRoutes.push(route);
270
- }
271
-
272
- (route as any).defineRoute = (...args: [any, any]) => defineRoute(route, ...args);
273
-
274
- (route as Route).handler = handler;
275
-
276
- return route as any; // this is usually ignored except for the root route.
277
- }
278
-
279
-
280
- export interface ZodAction<T extends z.ZodTypeAny, R extends JsonValue> {
281
- // (state: StateObject): Promise<typeof STREAM_ENDED>;
282
- inner: (route: z.output<T>) => Promise<R>
283
- zodRequest: (z: Z2<"JSON">) => T;
284
- zodResponse?: (z: Z2<"JSON">) => z.ZodType<R>;
285
- }
286
-
287
- export interface ZodRoute<
288
- M extends AllowedMethod,
289
- B extends BodyFormat,
290
- P extends Record<string, z.ZodTypeAny>,
291
- T extends z.ZodTypeAny,
292
- R extends JsonValue
293
- > extends ZodAction<T, R> {
294
- zodPathParams: (z: Z2<"STRING">) => P;
295
- method: M[];
296
- path: string;
297
- bodyFormat: B;
298
- inner: (state: ZodState<M, B, P, T>) => Promise<R>,
299
- }
300
-
301
- export class ZodState<
302
- M extends AllowedMethod,
303
- B extends BodyFormat,
304
- P extends Record<string, z.ZodTypeAny>,
305
- T extends z.ZodTypeAny
306
- > extends StateObject<B, M, string[][], z.output<T>> {
307
- declare pathParams: z.output<z.ZodObject<P>>;
308
- // declare data: z.output<T>;
309
- }
310
-
311
-
312
- export type RouterRouteMap<T> = {
313
- [K in keyof T as T[K] extends ZodAction<any, any> ? K : never]:
314
- T[K] extends {
315
- zodRequest: (z: any) => infer REQ extends z.ZodTypeAny,
316
- zodResponse?: (z: any) => infer RES extends z.ZodType<JsonValue>
317
- } ? ((data: z.input<REQ>) => Promise<jsonify<z.output<RES>>>) : never;
318
- }
319
-
320
- export type jsonify<T> =
321
- T extends void ? null :
322
- T extends Promise<any> ? unknown :
323
- T extends Date ? string :
324
- // T extends Map<infer K, infer V> ? [jsonify<K>, jsonify<V>][] :
325
- // T extends Set<infer U> ? jsonify<U>[] :
326
- T extends string | number | boolean | null | undefined ? T :
327
- T extends [...any[]] ? number extends T["length"] ? jsonify<T[number]>[] : [...jsonifyTuple<T>] :
328
- T extends Array<infer U> ? jsonify<U>[] :
329
- T extends object ? { [K in keyof T]: jsonify<T[K]> } :
330
- unknown;
331
-
332
- export type jsonifyTuple<T> = T extends [infer A, ...infer B] ? [jsonify<A>, ...jsonifyTuple<B>] : T extends [infer A] ? [jsonify<A>] : [];
333
-
334
-
335
-
336
- export type RouterKeyMap<T, V> = {
337
- [K in keyof T as T[K] extends ZodAction<any, any> ? K : never]: V;
338
- }
339
-
340
-
341
- // this is definitely the better version of defineRoute, but that was the starting point
342
- export function zodRoute<M extends AllowedMethod, B extends "GET" | "HEAD" extends M ? "ignore" : BodyFormat, P extends Record<string, z.ZodTypeAny>, T extends z.ZodTypeAny, R extends JsonValue>(
343
- method: M[],
344
- /** `"path/to/route/:var/route/:var2"` */
345
- path: string,
346
- zodPathParams: (z: Z2<"STRING">) => P,
347
- bodyFormat: B,
348
- zodRequest: (z: Z2<"JSON">) => T,
349
- inner: (state: ZodState<M, B, P, T>) => Promise<R>,
350
- ): ZodRoute<M, B, P, T, R> {
351
- return {
352
- method,
353
- path,
354
- bodyFormat,
355
- inner,
356
- zodRequest,
357
- zodPathParams,
358
- } as ZodRoute<M, B, P, T, R>;
359
- }
360
-
361
- export const registerZodRoutes = (root: rootRoute, router: any, keys: string[]) => {
362
- // const router = new TiddlerRouter();
363
- keys.forEach((key) => {
364
- const route = router[key as keyof typeof router] as ZodRoute<any, any, any, any, any>;
365
- const { method, path, bodyFormat, zodPathParams, zodRequest, inner } = route;
366
- const pathParams = path.split("/").filter(e => e.startsWith(":")).map(e => e.substring(1));
367
- ///^\/recipes\/([^\/]+)\/tiddlers\/(.+)$/,
368
- if (!path.startsWith("/")) throw new Error(`Path ${path} must start with a forward slash`);
369
- if (key.startsWith(":")) throw new Error(`Key ${key} must not start with a colon`)
370
- const pathregex = "^" + path.split("/").map(e =>
371
- e === "$key" ? key : e.startsWith(":") ? "([^/]+)" : e
372
- ).join("\\/") + "$";
373
-
374
- root.defineRoute({
375
- method: [...method, "OPTIONS"],
376
- path: new RegExp(pathregex),
377
- pathParams,
378
- bodyFormat,
379
- }, async state => {
380
- if (state.method === "OPTIONS") {
381
- return state.sendEmpty(200, {
382
- // "Access-Control-Allow-Origin": "*",
383
- "Access-Control-Allow-Methods": method.join(","),
384
- "Access-Control-Allow-Headers": "Accept, Content-Type, X-Requested-With",
385
- });
386
- }
387
- // return await (router[key] as ZodRoute<any, any, any, any, any>)(state);
388
- const pathCheck = Z2.object(zodPathParams(Z2)).safeParse(state.pathParams);
389
- if (!pathCheck.success) {
390
- console.log(pathCheck.error);
391
- throw state.sendEmpty(400, { "x-reason": "zod-path" });
392
- }
393
-
394
- const inputCheck = zodRequest(Z2).safeParse(state.data);
395
- if (!inputCheck.success) {
396
- console.log(inputCheck.error);
397
- throw state.sendEmpty(400, { "x-reason": "zod-request" });
398
- }
399
-
400
- const [good, error, res] = await inner(state)
401
- .then(e => [true, undefined, e] as const, e => [false, e, undefined] as const);
402
-
403
- if (!good) {
404
- if (error === STREAM_ENDED) {
405
- return error;
406
- } else if (typeof error === "string") {
407
- return state.sendString(400, { "x-reason": "zod-handler" }, error, "utf8");
408
- } else if (error instanceof Error && error.name === "UserError") {
409
- return state.sendString(400, { "x-reason": "user-error" }, error.message, "utf8");
410
- } else {
411
- throw error;
412
- }
413
- }
414
-
415
- return state.sendJSON(200, res);
416
- });
417
- });
418
- }
419
-
420
-
421
-
422
- export function zodManage<T extends z.ZodTypeAny, R extends JsonValue>(
423
- zodRequest: (z: Z2<"JSON">) => T,
424
- inner: (state: ZodState<"POST", "json", Record<string, z.ZodTypeAny>, T>, prisma: PrismaTxnClient) => Promise<R>
425
- ) {
426
- return zodRoute(["POST"], "/manager/$key", z => ({}), "json", zodRequest, async state => {
427
- return state.$transaction(async (prisma) => await inner(state, prisma));
428
- });
429
- }
@@ -1,178 +0,0 @@
1
-
2
- import * as opaque from "@serenity-kit/opaque";
3
- import { TypedGenerator } from "../../utils";
4
- import { randomBytes } from "node:crypto";
5
- import { ok } from "node:assert";
6
-
7
- export type PasswordService = Awaited<ReturnType<typeof createPasswordService>>;
8
-
9
- export async function createPasswordService(serverSetup: string) {
10
- await opaque.ready;
11
-
12
- const serverState = new Map<string, TypedGenerator<LoginGeneratorStates>>();
13
-
14
- const LoginGenerator = TypedGenerator.wrapper<LoginGeneratorStates>()(async function* ({
15
- user_id,
16
- startLoginRequest,
17
- registrationRecord,
18
- }: {
19
- user_id: PrismaField<"Users", "user_id">;
20
- startLoginRequest: string;
21
- registrationRecord: string;
22
- }): any {
23
-
24
- const { asV, asY } = TypedGenerator.checker<LoginGeneratorStates>();
25
-
26
- const { serverLoginState, loginResponse } = opaque.server.startLogin({
27
- serverSetup,
28
- userIdentifier: user_id.toString(),
29
- registrationRecord,
30
- startLoginRequest,
31
- });
32
-
33
- const timestamp = Date.now();
34
-
35
- const finishLoginRequest = asV(1, yield asY(0, loginResponse));
36
-
37
- // this is supposed to be programmatic
38
- // it should never last longer than five minutes
39
- if (timestamp + 1000 * 60 * 5 < Date.now()) return;
40
-
41
- // per the spec, the sessionKey will only be returned
42
- // if the client's session key has been verified.
43
- return asY(1, {
44
- session: opaque.server.finishLogin({
45
- finishLoginRequest,
46
- serverLoginState,
47
- }),
48
- user_id,
49
- });
50
-
51
- });
52
-
53
- /**
54
- * This code creates a password registration. It can be used for temporary passwords, if needed.
55
- */
56
- async function PasswordCreation(userID: string, password: string) {
57
- // client
58
-
59
- const { clientRegistrationState, registrationRequest } = opaque.client.startRegistration({ password });
60
-
61
- // server
62
- const userIdentifier = userID; // userId/email/username
63
-
64
- const { registrationResponse } = opaque.server.createRegistrationResponse({
65
- serverSetup,
66
- userIdentifier,
67
- registrationRequest,
68
- });
69
-
70
- // client
71
- const { registrationRecord } = opaque.client.finishRegistration({
72
- clientRegistrationState,
73
- registrationResponse,
74
- password,
75
- });
76
-
77
- return registrationRecord as PrismaField<"Users", "password">;
78
-
79
- }
80
-
81
-
82
-
83
- async function createRegistrationResponse({ userID, registrationRequest }: {
84
- userID: PrismaField<"Users", "user_id">;
85
- registrationRequest: string;
86
- }) {
87
-
88
- const { registrationResponse } = opaque.server.createRegistrationResponse({
89
- serverSetup,
90
- userIdentifier: userID.toString(),
91
- registrationRequest,
92
- });
93
-
94
- return registrationResponse;
95
-
96
- }
97
-
98
- async function startLoginSession(stater: TypedGenerator<LoginGeneratorStates>) {
99
- let loginSession;
100
- do { loginSession = randomBytes(16).toString("base64url"); }
101
- while (serverState.has(loginSession));
102
- serverState.set(loginSession, stater);
103
- return loginSession;
104
- }
105
-
106
- async function finishLoginSession(loginSession: string) {
107
- const stater = serverState.get(loginSession);
108
- if (stater) serverState.delete(loginSession);
109
- return stater;
110
- }
111
-
112
- return {
113
- serverState,
114
- LoginGenerator,
115
- PasswordCreation,
116
- createRegistrationResponse,
117
- startLoginSession,
118
- finishLoginSession
119
- }
120
-
121
- }
122
-
123
-
124
- type LoginGeneratorStates = [
125
- [void, loginResponse: string],
126
- [finishLoginRequest: string, {
127
- user_id: PrismaField<"Users", "user_id">,
128
- session: { sessionKey: string; } | undefined
129
- }]
130
- ]
131
-
132
-
133
-
134
-
135
- /**
136
- * This code verifies plain text passwords.
137
- * It shouldn't be used in production,
138
- * but this is how it would be done.
139
- */
140
- function PasswordVerification(userID: string, registrationRecord: string, password: string, serverSetup: string) {
141
-
142
- const { clientLoginState, startLoginRequest } = opaque.client.startLogin({ password, });
143
-
144
- // server
145
- const userIdentifier = userID;
146
-
147
- const { serverLoginState, loginResponse } = opaque.server.startLogin({
148
- serverSetup,
149
- userIdentifier,
150
- registrationRecord,
151
- startLoginRequest,
152
- });
153
-
154
- // client
155
- const loginResult = opaque.client.finishLogin({
156
- clientLoginState,
157
- loginResponse,
158
- password,
159
- });
160
-
161
- if (!loginResult) { throw new Error("Login failed"); }
162
-
163
- const { finishLoginRequest, sessionKey } = loginResult;
164
-
165
- // server
166
- // the server session key is only returned after verifying the client's response,
167
- // which validates that the client actually has the session key.
168
- const { sessionKey: serversessionkey } = opaque.server.finishLogin({
169
- finishLoginRequest,
170
- serverLoginState,
171
- });
172
-
173
- // This is never done in production, as the key must not be sent across the wire.
174
- ok(sessionKey === serversessionkey);
175
-
176
- return { sessionKey };
177
-
178
- }