(feat): better connection management, utility functions & tests
This commit is contained in:
parent
b8889812c5
commit
822dfea577
14 changed files with 448 additions and 39 deletions
55
dist/index.d.ts
vendored
55
dist/index.d.ts
vendored
|
|
@ -1,6 +1,6 @@
|
|||
import { type TransportClient, READY_STATE } from "@tensamin/ttp-core";
|
||||
import { createSchema } from "./schema";
|
||||
import type { ZodObject } from "zod";
|
||||
import type { z, ZodObject } from "zod";
|
||||
/**
|
||||
* @param redirectUrl The URL should point to the TAuth HTTP Server at http://hostname:port/callback
|
||||
*/
|
||||
|
|
@ -9,6 +9,7 @@ export declare class TAuthClient {
|
|||
identifier: string;
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
saveSession: (userId: number, sessionId: number) => Promise<void> | void;
|
||||
redirectUrl: URL;
|
||||
schema: ReturnType<typeof createSchema>;
|
||||
httpServer: {
|
||||
|
|
@ -17,31 +18,43 @@ export declare class TAuthClient {
|
|||
};
|
||||
clientMap: Map<string, TransportClient<{
|
||||
identification: {
|
||||
request: ZodObject<{
|
||||
app_identifier: import("zod").ZodString;
|
||||
app_session_id: import("zod").ZodNumber;
|
||||
app_public_key: import("zod").ZodString;
|
||||
user_id: import("zod").ZodNumber;
|
||||
}, import("zod/v4/core").$strip>;
|
||||
response: ZodObject<{
|
||||
challenge: import("zod").ZodBase64;
|
||||
public_key: import("zod").ZodBase64;
|
||||
}, import("zod/v4/core").$strip>;
|
||||
request: z.ZodObject<{
|
||||
app_identifier: z.ZodString;
|
||||
app_session_id: z.ZodNumber;
|
||||
app_public_key: z.ZodString;
|
||||
user_id: z.ZodNumber;
|
||||
}, z.core.$strip>;
|
||||
response: z.ZodObject<{
|
||||
challenge: z.ZodBase64;
|
||||
public_key: z.ZodBase64;
|
||||
}, z.core.$strip>;
|
||||
};
|
||||
challenge_response: {
|
||||
request: ZodObject<{
|
||||
challenge: import("zod").ZodBase64;
|
||||
}, import("zod/v4/core").$strip>;
|
||||
response: ZodObject<{
|
||||
app_data: ZodObject<import("zod/v4/core").$ZodLooseShape, import("zod/v4/core").$strip>;
|
||||
}, import("zod/v4/core").$strip>;
|
||||
request: z.ZodObject<{
|
||||
challenge: z.ZodBase64;
|
||||
}, z.core.$strip>;
|
||||
response: z.ZodObject<{}, z.core.$strip>;
|
||||
};
|
||||
load_app_data: {
|
||||
request: z.ZodObject<{}, z.core.$strip>;
|
||||
response: z.ZodObject<{
|
||||
app_data: z.ZodString;
|
||||
}, z.core.$strip>;
|
||||
};
|
||||
save_app_data: {
|
||||
request: z.ZodObject<{
|
||||
app_data: z.ZodString;
|
||||
}, z.core.$strip>;
|
||||
response: z.ZodObject<{}, z.core.$strip>;
|
||||
};
|
||||
}>>;
|
||||
constructor({ frontendUrl, identifier, privateKey, publicKey, redirectUrl, appData, httpServer, htmlSuccessPage, }: {
|
||||
private appData;
|
||||
constructor({ frontendUrl, identifier, privateKey, publicKey, saveSession, redirectUrl, appData, httpServer, htmlSuccessPage, }: {
|
||||
frontendUrl: string;
|
||||
identifier: string;
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
saveSession: (userId: number, sessionId: number) => Promise<void> | void;
|
||||
redirectUrl: string;
|
||||
appData: ZodObject;
|
||||
httpServer?: {
|
||||
|
|
@ -53,6 +66,9 @@ export declare class TAuthClient {
|
|||
generateChallenge(userId: number): Promise<string>;
|
||||
solveChallenge(userId: number, challenge: string): Promise<string>;
|
||||
createTTP(userId: number, sessionId: number, omikronUrl: string): void;
|
||||
loadData(userId: number, sessionId: number): Promise<Record<string, unknown>>;
|
||||
saveData(userId: number, sessionId: number, data: any): Promise<void>;
|
||||
private createTemporaryTTPConnection;
|
||||
generateLink(challenge?: string): string;
|
||||
}
|
||||
export declare function getFriendlyReadyState(stateIndex: number): keyof typeof READY_STATE;
|
||||
|
|
@ -60,3 +76,6 @@ export declare function generateKeyPair(): {
|
|||
private: string;
|
||||
public: string;
|
||||
};
|
||||
export * from "./crypto";
|
||||
export * from "./schema";
|
||||
export * from "./user";
|
||||
|
|
|
|||
57
dist/index.js
vendored
57
dist/index.js
vendored
|
|
@ -13,17 +13,24 @@ export class TAuthClient {
|
|||
identifier;
|
||||
privateKey;
|
||||
publicKey;
|
||||
saveSession;
|
||||
redirectUrl;
|
||||
schema;
|
||||
httpServer;
|
||||
clientMap = new Map();
|
||||
constructor({ frontendUrl, identifier, privateKey, publicKey, redirectUrl, appData, httpServer, htmlSuccessPage, }) {
|
||||
appData;
|
||||
constructor({ frontendUrl, identifier, privateKey, publicKey, saveSession, redirectUrl, appData, httpServer, htmlSuccessPage, }) {
|
||||
this.frontendUrl = frontendUrl;
|
||||
this.identifier = identifier;
|
||||
this.privateKey = privateKey;
|
||||
this.publicKey = publicKey;
|
||||
if (typeof saveSession !== "function") {
|
||||
throw new Error("[tauth] saveSession must be provided as a function");
|
||||
}
|
||||
this.saveSession = saveSession;
|
||||
this.redirectUrl = new URL(redirectUrl);
|
||||
this.schema = createSchema(appData);
|
||||
this.appData = appData;
|
||||
this.httpServer = httpServer ?? {
|
||||
port: 7878,
|
||||
hostname: "localhost",
|
||||
|
|
@ -62,8 +69,7 @@ export class TAuthClient {
|
|||
}
|
||||
if ((await this.solveChallenge(userId, originalChallenge)) ===
|
||||
potentialChallenge) {
|
||||
const { ip_address } = await fetch("https://omega.tensamin.net/api/get/omikron/" + userId).then((res) => res.json());
|
||||
this.createTTP(userId, potentialSessionId, ip_address);
|
||||
await this.saveSession(userId, potentialSessionId);
|
||||
return new Response(htmlSuccessPage || (await readFile("./index.html")), {
|
||||
headers: {
|
||||
"Content-Type": "text/html",
|
||||
|
|
@ -133,6 +139,48 @@ export class TAuthClient {
|
|||
}));
|
||||
this.clientMap.get(`${userId}:${sessionId}`)?.connect();
|
||||
}
|
||||
async loadData(userId, sessionId) {
|
||||
const connection = await this.createTemporaryTTPConnection(userId, sessionId);
|
||||
const rawAppData = await connection.send("load_app_data", {});
|
||||
const appData = JSON.parse(rawAppData.data.app_data);
|
||||
const safeAppData = this.appData.safeParse(appData);
|
||||
await connection.close();
|
||||
if (!safeAppData.success) {
|
||||
throw new Error(`Invalid app data: ${JSON.stringify(safeAppData.error.issues)}`);
|
||||
}
|
||||
return safeAppData.data;
|
||||
}
|
||||
async saveData(userId, sessionId, data) {
|
||||
const connection = await this.createTemporaryTTPConnection(userId, sessionId);
|
||||
const safeData = this.appData.safeParse(data);
|
||||
if (!safeData.success) {
|
||||
throw new Error(`Invalid app data: ${JSON.stringify(safeData.error.issues)}`);
|
||||
}
|
||||
await connection.send("save_app_data", {
|
||||
app_data: JSON.stringify(safeData.data),
|
||||
});
|
||||
await connection.close();
|
||||
}
|
||||
async createTemporaryTTPConnection(userId, sessionId) {
|
||||
const { ip_address } = await fetch("https://omega.tensamin.net/api/get/omikron/" + userId).then((res) => res.json());
|
||||
let temporaryClient;
|
||||
temporaryClient = createTransportClient(this.schema, {
|
||||
url: ip_address,
|
||||
onReadyStateChange: (stateIndex) => {
|
||||
const state = getFriendlyReadyState(stateIndex);
|
||||
if (state === "OPEN") {
|
||||
temporaryClient?.send("identification", {
|
||||
app_identifier: this.identifier,
|
||||
app_session_id: sessionId,
|
||||
app_public_key: this.publicKey,
|
||||
user_id: userId,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
temporaryClient.connect();
|
||||
return temporaryClient;
|
||||
}
|
||||
generateLink(challenge) {
|
||||
return new URL(`?identifier=${this.identifier}&redirect=${this.redirectUrl}${challenge ? `&challenge=${challenge}` : ""}`, this.frontendUrl).toString();
|
||||
}
|
||||
|
|
@ -159,3 +207,6 @@ export function generateKeyPair() {
|
|||
public: toBase64(pub),
|
||||
};
|
||||
}
|
||||
export * from "./crypto";
|
||||
export * from "./schema";
|
||||
export * from "./user";
|
||||
|
|
|
|||
12
dist/schema.d.ts
vendored
12
dist/schema.d.ts
vendored
|
|
@ -16,8 +16,18 @@ export declare function createSchema(appData: ZodObject): {
|
|||
request: z.ZodObject<{
|
||||
challenge: z.ZodBase64;
|
||||
}, z.core.$strip>;
|
||||
response: z.ZodObject<{}, z.core.$strip>;
|
||||
};
|
||||
load_app_data: {
|
||||
request: z.ZodObject<{}, z.core.$strip>;
|
||||
response: z.ZodObject<{
|
||||
app_data: z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>;
|
||||
app_data: z.ZodString;
|
||||
}, z.core.$strip>;
|
||||
};
|
||||
save_app_data: {
|
||||
request: z.ZodObject<{
|
||||
app_data: z.ZodString;
|
||||
}, z.core.$strip>;
|
||||
response: z.ZodObject<{}, z.core.$strip>;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
12
dist/schema.js
vendored
12
dist/schema.js
vendored
|
|
@ -17,9 +17,19 @@ export function createSchema(appData) {
|
|||
request: z.object({
|
||||
challenge: z.base64(),
|
||||
}),
|
||||
response: z.object({}),
|
||||
},
|
||||
load_app_data: {
|
||||
request: z.object({}),
|
||||
response: z.object({
|
||||
app_data: appData,
|
||||
app_data: z.string(),
|
||||
}),
|
||||
},
|
||||
save_app_data: {
|
||||
request: z.object({
|
||||
app_data: z.string(),
|
||||
}),
|
||||
response: z.object({}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
1
dist/user.d.ts
vendored
1
dist/user.d.ts
vendored
|
|
@ -10,5 +10,4 @@ export type User = {
|
|||
status_message: string;
|
||||
about: string;
|
||||
};
|
||||
export declare const userCacheMap: Map<number, User>;
|
||||
export declare function get(userId: number): Promise<User | undefined>;
|
||||
|
|
|
|||
2
dist/user.js
vendored
2
dist/user.js
vendored
|
|
@ -1,4 +1,4 @@
|
|||
export const userCacheMap = new Map();
|
||||
const userCacheMap = new Map();
|
||||
export async function get(userId) {
|
||||
try {
|
||||
const cachedUser = userCacheMap.get(userId);
|
||||
|
|
|
|||
Loading…
Reference in a new issue