feat: react zitadel
This commit is contained in:
4
platform/config/mod.ts
Normal file
4
platform/config/mod.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from "./dotenv.ts";
|
||||
export * from "./environment.ts";
|
||||
export * from "./errors.ts";
|
||||
export * from "./service.ts";
|
||||
@@ -3,8 +3,12 @@
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./mod.ts",
|
||||
"exports": {
|
||||
".": "./mod.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@std/dotenv": "npm:@jsr/std__dotenv@0.225.5",
|
||||
"zod": "4.1.11"
|
||||
"zod": "4.1.12"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import type { Collection, CollectionOptions, Db, Document, MongoClient } from "mongodb";
|
||||
|
||||
import { mongo } from "./client.ts";
|
||||
|
||||
export function getDatabaseAccessor<TSchemas extends Record<string, Document>>(
|
||||
database: string,
|
||||
): DatabaseAccessor<TSchemas> {
|
||||
let instance: Db | undefined;
|
||||
return {
|
||||
get db(): Db {
|
||||
if (instance === undefined) {
|
||||
instance = this.client.db(database);
|
||||
}
|
||||
return instance;
|
||||
},
|
||||
get client(): MongoClient {
|
||||
return mongo;
|
||||
},
|
||||
collection<TSchema extends keyof TSchemas>(
|
||||
name: TSchema,
|
||||
options?: CollectionOptions,
|
||||
): Collection<TSchemas[TSchema]> {
|
||||
return this.db.collection<TSchemas[TSchema]>(name.toString(), options);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export type DatabaseAccessor<TSchemas extends Record<string, Document>> = {
|
||||
/**
|
||||
* Database for given accessor.
|
||||
*/
|
||||
db: Db;
|
||||
|
||||
/**
|
||||
* Lazy loaded mongo client.
|
||||
*/
|
||||
client: MongoClient;
|
||||
|
||||
/**
|
||||
* Returns a reference to a MongoDB Collection. If it does not exist it will be created implicitly.
|
||||
*
|
||||
* Collection namespace validation is performed server-side.
|
||||
*
|
||||
* @param name - Collection name we wish to access.
|
||||
* @param options - Optional settings for the command.
|
||||
*/
|
||||
collection<TSchema extends keyof TSchemas>(name: TSchema, options?: CollectionOptions): Collection<TSchemas[TSchema]>;
|
||||
};
|
||||
@@ -1,4 +1,121 @@
|
||||
import { config } from "./config.ts";
|
||||
import { getMongoClient } from "./connection.ts";
|
||||
import { AsyncLocalStorage } from "node:async_hooks";
|
||||
|
||||
export const mongo = getMongoClient(config.mongo);
|
||||
import postgres, { type Options, type Sql, type TransactionSql } from "postgres";
|
||||
import type { ZodType } from "zod";
|
||||
|
||||
import { takeAll, takeOne } from "./parser.ts";
|
||||
|
||||
const storage = new AsyncLocalStorage<TransactionSql>();
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------------
|
||||
| Database
|
||||
|--------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export class Client {
|
||||
/**
|
||||
* Cached SQL instance.
|
||||
*/
|
||||
#db?: Sql;
|
||||
|
||||
/**
|
||||
* Instantiate a new Database accessor wrapper.
|
||||
*
|
||||
* @param db - Dependency container token to retrieve.
|
||||
*/
|
||||
constructor(readonly config: Options<{}>) {}
|
||||
|
||||
/**
|
||||
* SQL instance to perform queries against.
|
||||
*/
|
||||
get sql(): Sql {
|
||||
const tx = storage.getStore();
|
||||
if (tx !== undefined) {
|
||||
return tx;
|
||||
}
|
||||
return this.#getResolvedInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL instance which ignores any potential transaction established
|
||||
* in instance scope.
|
||||
*/
|
||||
get direct(): Sql {
|
||||
return this.#getResolvedInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves cached SQL instance or attempts to create and return
|
||||
* a new instance.
|
||||
*/
|
||||
#getResolvedInstance(): Sql {
|
||||
if (this.#db === undefined) {
|
||||
this.#db = postgres(this.config);
|
||||
}
|
||||
return this.#db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a SQL transaction by wrapping a new db instance with a
|
||||
* new transaction instance.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { db } from "@optio/database/client.ts";
|
||||
*
|
||||
* db.begin(async (tx) => {
|
||||
* tx`SELECT ...`
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
begin<TResponse>(cb: (tx: TransactionSql) => TResponse | Promise<TResponse>): Promise<UnwrapPromiseArray<TResponse>> {
|
||||
return this.direct.begin((tx) => storage.run(tx, () => cb(tx)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes SQL connection if it has been instantiated.
|
||||
*/
|
||||
async close(): Promise<void> {
|
||||
if (this.#db !== undefined) {
|
||||
await this.#db.end();
|
||||
this.#db = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a schema pepared querying object allowing for a one or many
|
||||
* response based on the query used.
|
||||
*
|
||||
* @param schema - Zod schema to parse.
|
||||
*/
|
||||
schema<TSchema extends ZodType>(schema: TSchema) {
|
||||
return {
|
||||
/**
|
||||
* Executes a sql query and parses the result with the given schema.
|
||||
*
|
||||
* @param sql - Template string SQL value.
|
||||
*/
|
||||
one: (strings: TemplateStringsArray, ...values: any[]) => this.sql(strings, ...values).then(takeOne(schema)),
|
||||
|
||||
/**
|
||||
* Executes a sql query and parses the resulting list with the given schema.
|
||||
*
|
||||
* @param sql - Template string SQL value.
|
||||
*/
|
||||
many: (strings: TemplateStringsArray, ...values: any[]) => this.sql(strings, ...values).then(takeAll(schema)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------------
|
||||
| Types
|
||||
|--------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
type UnwrapPromiseArray<T> = T extends any[]
|
||||
? {
|
||||
[k in keyof T]: T[k] extends Promise<infer R> ? R : T[k];
|
||||
}
|
||||
: T;
|
||||
|
||||
@@ -2,26 +2,26 @@ import { getEnvironmentVariable } from "@platform/config/environment.ts";
|
||||
import z from "zod";
|
||||
|
||||
export const config = {
|
||||
mongo: {
|
||||
xtdb: {
|
||||
host: getEnvironmentVariable({
|
||||
key: "DB_MONGO_HOST",
|
||||
key: "DB_XTDB_HOST",
|
||||
type: z.string(),
|
||||
fallback: "localhost",
|
||||
}),
|
||||
port: getEnvironmentVariable({
|
||||
key: "DB_MONGO_PORT",
|
||||
key: "DB_XTDB_PORT",
|
||||
type: z.coerce.number(),
|
||||
fallback: "67017",
|
||||
fallback: "5432",
|
||||
}),
|
||||
user: getEnvironmentVariable({
|
||||
key: "DB_MONGO_USER",
|
||||
key: "DB_XTDB_USER",
|
||||
type: z.string(),
|
||||
fallback: "root",
|
||||
fallback: "xtdb",
|
||||
}),
|
||||
pass: getEnvironmentVariable({
|
||||
key: "DB_MONGO_PASSWORD",
|
||||
key: "DB_XTDB_PASSWORD",
|
||||
type: z.string(),
|
||||
fallback: "password",
|
||||
fallback: "xtdb",
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { MongoClient } from "mongodb";
|
||||
|
||||
export function getMongoClient(config: MongoConnectionInfo) {
|
||||
return new MongoClient(getConnectionUrl(config));
|
||||
}
|
||||
|
||||
export function getConnectionUrl({ host, port, user, pass }: MongoConnectionInfo): MongoConnectionUrl {
|
||||
return `mongodb://${user}:${pass}@${host}:${port}`;
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------------
|
||||
| Types
|
||||
|--------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export type MongoConnectionUrl = `mongodb://${string}:${string}@${string}:${number}`;
|
||||
|
||||
export type MongoConnectionInfo = {
|
||||
host: string;
|
||||
port: number;
|
||||
user: string;
|
||||
pass: string;
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
import type { CreateIndexesOptions, IndexSpecification } from "mongodb";
|
||||
|
||||
export const idIndex: [IndexSpecification, CreateIndexesOptions] = [{ id: 1 }, { unique: true }];
|
||||
@@ -5,8 +5,7 @@
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@platform/config": "workspace:*",
|
||||
"@valkyr/inverse": "npm:@jsr/valkyr__inverse@1.0.1",
|
||||
"mongodb": "6.20.0",
|
||||
"zod": "4.1.11"
|
||||
"postgres": "3.4.7",
|
||||
"zod": "4.1.12"
|
||||
}
|
||||
}
|
||||
|
||||
29
platform/database/parser.ts
Normal file
29
platform/database/parser.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type z from "zod";
|
||||
import type { ZodType } from "zod";
|
||||
|
||||
/**
|
||||
* Takes a single record from a list of database rows.
|
||||
*
|
||||
* @param rows - List of rows to retrieve record from.
|
||||
*/
|
||||
export function takeOne<TSchema extends ZodType>(
|
||||
schema: TSchema,
|
||||
): (records: unknown[]) => z.output<TSchema> | undefined {
|
||||
return (records: unknown[]) => {
|
||||
if (records[0] === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return schema.parse(records[0]);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes all records from a list of database rows and validates each one.
|
||||
*
|
||||
* @param schema - Zod schema to validate each record against.
|
||||
*/
|
||||
export function takeAll<TSchema extends ZodType>(schema: TSchema): (records: unknown[]) => z.output<TSchema>[] {
|
||||
return (records: unknown[]) => {
|
||||
return records.map((record) => schema.parse(record));
|
||||
};
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import type { CreateIndexesOptions, Db, IndexSpecification } from "mongodb";
|
||||
|
||||
import { getCollectionsSet } from "./utilities.ts";
|
||||
|
||||
/**
|
||||
* Takes a mongo database and registers the event store collections and
|
||||
* indexes defined internally.
|
||||
*
|
||||
* @param db - Mongo database to register event store collections against.
|
||||
* @param registrars - List of registrars to register with the database.
|
||||
* @param logger - Logger method to print internal logs.
|
||||
*/
|
||||
export async function register(db: Db, registrars: Registrar[], logger?: (...args: any[]) => any) {
|
||||
const list = await getCollectionsSet(db);
|
||||
for (const { name, indexes } of registrars) {
|
||||
if (list.has(name) === false) {
|
||||
await db.createCollection(name);
|
||||
}
|
||||
for (const [indexSpec, options] of indexes) {
|
||||
await db.collection(name).createIndex(indexSpec, options);
|
||||
logger?.("Mongo Event Store > Collection '%s' is indexed [%O] with options %O", name, indexSpec, options ?? {});
|
||||
}
|
||||
logger?.("Mongo Event Store > Collection '%s' is registered", name);
|
||||
}
|
||||
}
|
||||
|
||||
export type Registrar = {
|
||||
name: string;
|
||||
indexes: [IndexSpecification, CreateIndexesOptions?][];
|
||||
};
|
||||
@@ -1,70 +0,0 @@
|
||||
import type { Db } from "mongodb";
|
||||
import type { ZodObject, ZodType, z } from "zod";
|
||||
|
||||
/**
|
||||
* TODO ...
|
||||
*/
|
||||
export function takeOne<TDocument>(documents: TDocument[]): TDocument | undefined {
|
||||
return documents[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO ...
|
||||
*/
|
||||
export function makeDocumentParser<TSchema extends ZodObject>(schema: TSchema): ModelParserFn<TSchema> {
|
||||
return ((value: unknown | unknown[]) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((value: unknown) => schema.parse(value));
|
||||
}
|
||||
if (value === undefined || value === null) {
|
||||
return undefined;
|
||||
}
|
||||
return schema.parse(value);
|
||||
}) as ModelParserFn<TSchema>;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO ...
|
||||
*/
|
||||
export function toParsedDocuments<TSchema extends ZodType>(
|
||||
schema: TSchema,
|
||||
): (documents: unknown[]) => Promise<z.infer<TSchema>[]> {
|
||||
return async (documents: unknown[]) => {
|
||||
const parsed = [];
|
||||
for (const document of documents) {
|
||||
parsed.push(await schema.parseAsync(document));
|
||||
}
|
||||
return parsed;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO ...
|
||||
*/
|
||||
export function toParsedDocument<TSchema extends ZodType>(
|
||||
schema: TSchema,
|
||||
): (document?: unknown) => Promise<z.infer<TSchema> | undefined> {
|
||||
return async (document: unknown) => {
|
||||
if (document === undefined || document === null) {
|
||||
return undefined;
|
||||
}
|
||||
return schema.parseAsync(document);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Set of collections that exists on a given mongo database instance.
|
||||
*
|
||||
* @param db - Mongo database to fetch collection list for.
|
||||
*/
|
||||
export async function getCollectionsSet(db: Db) {
|
||||
return db
|
||||
.listCollections()
|
||||
.toArray()
|
||||
.then((collections) => new Set(collections.map((c) => c.name)));
|
||||
}
|
||||
|
||||
type ModelParserFn<TSchema extends ZodObject> = {
|
||||
(value: unknown): z.infer<TSchema> | undefined;
|
||||
(value: unknown[]): z.infer<TSchema>[];
|
||||
};
|
||||
@@ -10,6 +10,6 @@
|
||||
"dependencies": {
|
||||
"@platform/config": "workspace:*",
|
||||
"@valkyr/event-store": "npm:@jsr/valkyr__event-store@2.0.1",
|
||||
"zod": "4.1.11"
|
||||
"zod": "4.1.12"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { encrypt } from "@platform/vault";
|
||||
|
||||
import {
|
||||
assertServerErrorResponse,
|
||||
type RelayAdapter,
|
||||
@@ -69,10 +67,7 @@ export class HttpAdapter implements RelayAdapter {
|
||||
return `${this.url}${endpoint}`;
|
||||
}
|
||||
|
||||
async send(
|
||||
{ method, endpoint, query, body, headers = new Headers() }: RelayInput,
|
||||
publicKey: string,
|
||||
): Promise<RelayResponse> {
|
||||
async send({ method, endpoint, query, body, headers = new Headers() }: RelayInput): Promise<RelayResponse> {
|
||||
const init: RequestInit = { method, headers };
|
||||
|
||||
// ### Before Request
|
||||
@@ -95,14 +90,6 @@ export class HttpAdapter implements RelayAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
// ### Internal
|
||||
// If public key is present we create a encrypted token on the header that
|
||||
// is verified by the server before allowing the request through.
|
||||
|
||||
if (publicKey !== undefined) {
|
||||
headers.set("x-internal", await encrypt("internal", publicKey));
|
||||
}
|
||||
|
||||
// ### Response
|
||||
|
||||
return this.request(`${endpoint}${query}`, init);
|
||||
@@ -138,6 +125,9 @@ export class HttpAdapter implements RelayAdapter {
|
||||
* @param body - Request body.
|
||||
*/
|
||||
#getRequestFormat(body: unknown): "form-data" | "json" {
|
||||
if (body instanceof FormData) {
|
||||
return "form-data";
|
||||
}
|
||||
if (containsFile(body) === true) {
|
||||
return "form-data";
|
||||
}
|
||||
@@ -245,14 +235,30 @@ export class HttpAdapter implements RelayAdapter {
|
||||
};
|
||||
}
|
||||
|
||||
// ### Error
|
||||
// If the 'content-type' is not a JSON response from the API then we check if the
|
||||
// response status is an error code.
|
||||
|
||||
if (response.status >= 400) {
|
||||
return {
|
||||
result: "error",
|
||||
headers: response.headers,
|
||||
error: {
|
||||
code: "SERVER_ERROR_RESPONSE",
|
||||
status: response.status,
|
||||
message: await response.text(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ### Success
|
||||
// If the 'content-type' is not a JSON response from the API and the request is not
|
||||
// an error we simply return the pure response in the data key.
|
||||
|
||||
return {
|
||||
result: "error",
|
||||
result: "success",
|
||||
headers: response.headers,
|
||||
error: {
|
||||
code: "UNSUPPORTED_CONTENT_TYPE",
|
||||
status: response.status,
|
||||
message: "Unsupported 'content-type' in header returned from server.",
|
||||
},
|
||||
data: response,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ function getRouteFn(route: Route, { adapter }: Config) {
|
||||
|
||||
// ### Fetch
|
||||
|
||||
const response = await adapter.send(input, route.state.crypto?.publicKey);
|
||||
const response = await adapter.send(input);
|
||||
|
||||
if ("data" in response && route.state.response !== undefined) {
|
||||
response.data = route.state.response.parse(response.data);
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
export type Hooks = {
|
||||
/**
|
||||
* Executes when any error is thrown before or during the lifetime
|
||||
* of the route. This allows for custom handling of errors if the
|
||||
* route has unique requirements to error handling.
|
||||
*
|
||||
* @param error - Error which has been thrown.
|
||||
*/
|
||||
onError?: (error: unknown) => Response;
|
||||
};
|
||||
@@ -3,7 +3,6 @@ import z, { type ZodObject, type ZodRawShape, type ZodType } from "zod";
|
||||
|
||||
import type { ServerContext } from "./context.ts";
|
||||
import { ServerError, type ServerErrorClass } from "./errors.ts";
|
||||
import type { Hooks } from "./hooks.ts";
|
||||
|
||||
export class Route<const TState extends RouteState = RouteState> {
|
||||
readonly type = "route" as const;
|
||||
@@ -85,23 +84,6 @@ export class Route<const TState extends RouteState = RouteState> {
|
||||
return new Route({ ...this.state, meta });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cryptographic keys used to resolve cryptographic requests.
|
||||
*
|
||||
* @param crypto - Crypto configuration object.
|
||||
*
|
||||
* @examples
|
||||
*
|
||||
* ```ts
|
||||
* route.post("/foo").crypto({ publicKey: "..." });
|
||||
* ```
|
||||
*/
|
||||
crypto<TCrypto extends { publicKey: string }>(
|
||||
crypto: TCrypto,
|
||||
): Route<Prettify<Omit<TState, "crypto"> & { crypto: TCrypto }>> {
|
||||
return new Route({ ...this.state, crypto });
|
||||
}
|
||||
|
||||
/**
|
||||
* Access level of the route which acts as the first barrier of entry
|
||||
* to ensure that requests are valid.
|
||||
@@ -307,19 +289,6 @@ export class Route<const TState extends RouteState = RouteState> {
|
||||
): Route<Omit<TState, "handle"> & { handle: THandleFn }> {
|
||||
return new Route({ ...this.state, handle });
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign lifetime hooks to a route allowing for custom handling of
|
||||
* events that can occur during a request or response.
|
||||
*
|
||||
* Can be used on both server and client with the appropriate
|
||||
* implementation.
|
||||
*
|
||||
* @param hooks - Hooks to register with the route.
|
||||
*/
|
||||
hooks<THooks extends Hooks>(hooks: THooks): Route<Prettify<Omit<TState, "hooks"> & { hooks: THooks }>> {
|
||||
return new Route({ ...this.state, hooks });
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -451,9 +420,6 @@ export type RouteFn = (...args: any[]) => any;
|
||||
type RouteState = {
|
||||
method: RouteMethod;
|
||||
path: string;
|
||||
crypto?: {
|
||||
publicKey: string;
|
||||
};
|
||||
meta?: RouteMeta;
|
||||
access?: RouteAccess;
|
||||
params?: ZodObject;
|
||||
@@ -462,7 +428,6 @@ type RouteState = {
|
||||
errors: ServerErrorClass[];
|
||||
response?: ZodType;
|
||||
handle?: HandleFn;
|
||||
hooks?: Hooks;
|
||||
};
|
||||
|
||||
export type RouteMeta = {
|
||||
@@ -474,7 +439,7 @@ export type RouteMeta = {
|
||||
|
||||
export type RouteMethod = "POST" | "GET" | "PUT" | "PATCH" | "DELETE";
|
||||
|
||||
export type RouteAccess = "public" | "session" | ["internal:public", string] | ["internal:session", string];
|
||||
export type RouteAccess = "public" | "authenticated";
|
||||
|
||||
type HandleFn<TArgs extends Array<any> = any[], TResponse = any> = (
|
||||
...args: TArgs
|
||||
|
||||
@@ -3,6 +3,5 @@ export * from "./libraries/adapter.ts";
|
||||
export * from "./libraries/client.ts";
|
||||
export * from "./libraries/context.ts";
|
||||
export * from "./libraries/errors.ts";
|
||||
export * from "./libraries/hooks.ts";
|
||||
export * from "./libraries/procedure.ts";
|
||||
export * from "./libraries/route.ts";
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
"@platform/auth": "workspace:*",
|
||||
"@platform/socket": "workspace:*",
|
||||
"@platform/supertokens": "workspace:*",
|
||||
"@platform/vault": "workspace:*",
|
||||
"path-to-regexp": "8",
|
||||
"zod": "4.1.11"
|
||||
"zod": "4.1.12"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@platform/relay": "workspace:*",
|
||||
"zod": "4.1.11"
|
||||
"zod": "4.1.12"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { logger } from "@platform/logger";
|
||||
import {
|
||||
BadRequestError,
|
||||
context,
|
||||
ForbiddenError,
|
||||
InternalServerError,
|
||||
NotFoundError,
|
||||
NotImplementedError,
|
||||
@@ -13,7 +12,6 @@ import {
|
||||
UnauthorizedError,
|
||||
ValidationError,
|
||||
} from "@platform/relay";
|
||||
import { decrypt } from "@platform/vault";
|
||||
|
||||
const SUPPORTED_MEHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
||||
|
||||
@@ -98,7 +96,7 @@ export class Api {
|
||||
// Execute request and return a response.
|
||||
|
||||
const response = await this.#getRouteResponse(resolved, request).catch((error) =>
|
||||
this.#getErrorResponse(error, resolved.route, request),
|
||||
this.#getErrorResponse(error, request),
|
||||
);
|
||||
|
||||
return response;
|
||||
@@ -164,31 +162,10 @@ export class Api {
|
||||
);
|
||||
}
|
||||
|
||||
if (route.state.access === "session" && context.isAuthenticated === false) {
|
||||
if (route.state.access === "authenticated" && context.isAuthenticated === false) {
|
||||
return toResponse(new UnauthorizedError(), request);
|
||||
}
|
||||
|
||||
if (Array.isArray(route.state.access)) {
|
||||
const [access, privateKey] = route.state.access;
|
||||
const value = request.headers.get("x-internal");
|
||||
if (value === null) {
|
||||
return toResponse(
|
||||
new ForbiddenError(`Route '${route.method} ${route.path}' is missing 'x-internal' token.`),
|
||||
request,
|
||||
);
|
||||
}
|
||||
const decrypted = await decrypt<string>(value, privateKey);
|
||||
if (decrypted !== "internal") {
|
||||
return toResponse(
|
||||
new ForbiddenError(`Route '${route.method} ${route.path}' has invalid 'x-internal' token.`),
|
||||
request,
|
||||
);
|
||||
}
|
||||
if (access === "internal:session" && context.isAuthenticated === false) {
|
||||
return toResponse(new UnauthorizedError(), request);
|
||||
}
|
||||
}
|
||||
|
||||
// ### Params
|
||||
// If the route has params we want to coerce the values to the expected types.
|
||||
|
||||
@@ -242,10 +219,7 @@ export class Api {
|
||||
return toResponse(await route.state.handle(...args), request);
|
||||
}
|
||||
|
||||
#getErrorResponse(error: unknown, route: Route, request: Request): Response {
|
||||
if (route?.state.hooks?.onError !== undefined) {
|
||||
return route.state.hooks.onError(error);
|
||||
}
|
||||
#getErrorResponse(error: unknown, request: Request): Response {
|
||||
if (error instanceof ServerError) {
|
||||
return toResponse(error, request);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
"@platform/socket": "workspace:*",
|
||||
"@platform/storage": "workspace:*",
|
||||
"@valkyr/json-rpc": "npm:@jsr/valkyr__json-rpc@1.1.0",
|
||||
"zod": "4.1.11"
|
||||
"zod": "4.1.12"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
"dependencies": {
|
||||
"@platform/models": "workspace:*",
|
||||
"@platform/relay": "workspace:*",
|
||||
"zod": "4.1.11"
|
||||
"zod": "4.1.12"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user