feat: add functional authentication
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { Auth, ResolvedSession } from "@valkyr/auth";
|
||||
import z from "zod";
|
||||
|
||||
import { db } from "~libraries/read-store/database.ts";
|
||||
import { db } from "~stores/read-store/database.ts";
|
||||
|
||||
import { config } from "./config.ts";
|
||||
|
||||
@@ -11,18 +11,13 @@ export const auth = new Auth(
|
||||
algorithm: "RS256",
|
||||
privateKey: config.privateKey,
|
||||
publicKey: config.publicKey,
|
||||
issuer: "https://balto.health",
|
||||
audience: "https://balto.health",
|
||||
issuer: "http://localhost",
|
||||
audience: "http://localhost",
|
||||
},
|
||||
session: z.object({
|
||||
accountId: z.string(),
|
||||
}),
|
||||
permissions: {
|
||||
admin: ["create", "read", "update", "delete"],
|
||||
organization: ["create", "read", "update", "delete"],
|
||||
consultant: ["create", "read", "update", "delete"],
|
||||
task: ["create", "update", "read", "delete"],
|
||||
} as const,
|
||||
permissions: {} as const,
|
||||
guards: [],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
import { Strategy } from "@spec/modules/account/strategies.ts";
|
||||
import { Avatar, Contact, Email, Name } from "@spec/shared";
|
||||
import { AggregateRoot, getDate } from "@valkyr/event-store";
|
||||
|
||||
import { db } from "~libraries/read-store/mod.ts";
|
||||
|
||||
import { eventStore } from "../event-store.ts";
|
||||
import { Auditor } from "../events/auditor.ts";
|
||||
import { EventStoreFactory } from "../events/mod.ts";
|
||||
import { projector } from "../projector.ts";
|
||||
|
||||
export class Account extends AggregateRoot<EventStoreFactory> {
|
||||
static override readonly name = "account";
|
||||
|
||||
avatar?: Avatar;
|
||||
name?: Name;
|
||||
contact: Contact = {
|
||||
emails: [],
|
||||
};
|
||||
strategies: Strategy[] = [];
|
||||
|
||||
createdAt!: Date;
|
||||
updatedAt!: Date;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Reducer
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
with(event: EventStoreFactory["$events"][number]["$record"]): void {
|
||||
switch (event.type) {
|
||||
case "account:avatar:added": {
|
||||
this.avatar = { url: event.data };
|
||||
this.updatedAt = getDate(event.created);
|
||||
break;
|
||||
}
|
||||
case "account:name:added": {
|
||||
this.name = event.data;
|
||||
this.updatedAt = getDate(event.created);
|
||||
break;
|
||||
}
|
||||
case "account:email:added": {
|
||||
this.contact.emails.push(event.data);
|
||||
this.updatedAt = getDate(event.created);
|
||||
break;
|
||||
}
|
||||
case "strategy:email:added": {
|
||||
this.strategies.push({ type: "email", value: event.data });
|
||||
this.updatedAt = getDate(event.created);
|
||||
break;
|
||||
}
|
||||
case "strategy:password:added": {
|
||||
this.strategies.push({ type: "password", ...event.data });
|
||||
this.updatedAt = getDate(event.created);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Actions
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
addAvatar(url: string, meta: Auditor): this {
|
||||
return this.push({
|
||||
stream: this.id,
|
||||
type: "account:avatar:added",
|
||||
data: url,
|
||||
meta,
|
||||
});
|
||||
}
|
||||
|
||||
addName(name: Name, meta: Auditor): this {
|
||||
return this.push({
|
||||
stream: this.id,
|
||||
type: "account:name:added",
|
||||
data: name,
|
||||
meta,
|
||||
});
|
||||
}
|
||||
|
||||
addEmail(email: Email, meta: Auditor): this {
|
||||
return this.push({
|
||||
stream: this.id,
|
||||
type: "account:email:added",
|
||||
data: email,
|
||||
meta,
|
||||
});
|
||||
}
|
||||
|
||||
addRole(roleId: string, meta: Auditor): this {
|
||||
return this.push({
|
||||
stream: this.id,
|
||||
type: "account:role:added",
|
||||
data: roleId,
|
||||
meta,
|
||||
});
|
||||
}
|
||||
|
||||
addEmailStrategy(email: string, meta: Auditor): this {
|
||||
return this.push({
|
||||
stream: this.id,
|
||||
type: "strategy:email:added",
|
||||
data: email,
|
||||
meta,
|
||||
});
|
||||
}
|
||||
|
||||
addPasswordStrategy(alias: string, password: string, meta: Auditor): this {
|
||||
return this.push({
|
||||
stream: this.id,
|
||||
type: "strategy:password:added",
|
||||
data: { alias, password },
|
||||
meta,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------------
|
||||
| Projectors
|
||||
|--------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
projector.on("account:avatar:added", async ({ stream: id, data: url }) => {
|
||||
await db.collection("accounts").updateOne({ id }, { $set: { avatar: { url } } }, { upsert: true });
|
||||
});
|
||||
|
||||
projector.on("account:name:added", async ({ stream: id, data: name }) => {
|
||||
await db.collection("accounts").updateOne({ id }, { $set: { name } }, { upsert: true });
|
||||
});
|
||||
|
||||
projector.on("account:email:added", async ({ stream: id, data: email }) => {
|
||||
await db.collection("accounts").updateOne({ id }, { $push: { "contact.emails": email } }, { upsert: true });
|
||||
});
|
||||
|
||||
projector.on("account:role:added", async ({ stream: id, data: roleId }) => {
|
||||
await db.collection("accounts").updateOne({ id }, { $push: { roles: roleId } }, { upsert: true });
|
||||
});
|
||||
|
||||
projector.on("strategy:email:added", async ({ stream: id, data: email }) => {
|
||||
await eventStore.relations.insert(`account:email:${email}`, id);
|
||||
await db
|
||||
.collection("accounts")
|
||||
.updateOne({ id }, { $push: { strategies: { type: "email", value: email } } }, { upsert: true });
|
||||
});
|
||||
|
||||
projector.on("strategy:password:added", async ({ stream: id, data: strategy }) => {
|
||||
await eventStore.relations.insert(`account:alias:${strategy.alias}`, id);
|
||||
await db
|
||||
.collection("accounts")
|
||||
.updateOne({ id }, { $push: { strategies: { type: "password", ...strategy } } }, { upsert: true });
|
||||
});
|
||||
@@ -1,78 +0,0 @@
|
||||
import { AggregateRoot, getDate, makeAggregateReducer } from "@valkyr/event-store";
|
||||
|
||||
import { CodeIdentity } from "../events/code.ts";
|
||||
import { EventStoreFactory } from "../events/mod.ts";
|
||||
|
||||
export class Code extends AggregateRoot<EventStoreFactory> {
|
||||
static override readonly name = "code";
|
||||
|
||||
id!: string;
|
||||
|
||||
identity!: CodeIdentity;
|
||||
value!: string;
|
||||
|
||||
createdAt!: Date;
|
||||
claimedAt?: Date;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Factories
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static #reducer = makeAggregateReducer(Code);
|
||||
|
||||
static create(identity: CodeIdentity): Code {
|
||||
return new Code().push({
|
||||
type: "code:created",
|
||||
data: {
|
||||
identity,
|
||||
value: crypto
|
||||
.getRandomValues(new Uint8Array(5))
|
||||
.map((v) => v % 10)
|
||||
.join(""),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static async getById(stream: string): Promise<Code | undefined> {
|
||||
return this.$store.reduce({
|
||||
name: "code",
|
||||
stream,
|
||||
reducer: this.#reducer,
|
||||
});
|
||||
}
|
||||
|
||||
get isClaimed(): boolean {
|
||||
return this.claimedAt !== undefined;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Folder
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
with(event: EventStoreFactory["$events"][number]["$record"]): void {
|
||||
switch (event.type) {
|
||||
case "code:created": {
|
||||
this.id = event.stream;
|
||||
this.value = event.data.value;
|
||||
this.identity = event.data.identity;
|
||||
this.createdAt = getDate(event.created);
|
||||
break;
|
||||
}
|
||||
case "code:claimed": {
|
||||
this.claimedAt = getDate(event.created);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Actions
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
claim(): this {
|
||||
return this.push({
|
||||
type: "code:claimed",
|
||||
stream: this.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import { AggregateRoot, getDate, makeAggregateReducer } from "@valkyr/event-store";
|
||||
|
||||
import { db } from "~libraries/read-store/mod.ts";
|
||||
|
||||
import { Auditor } from "../events/auditor.ts";
|
||||
import { EventStoreFactory } from "../events/mod.ts";
|
||||
import { projector } from "../projector.ts";
|
||||
|
||||
export class Organization extends AggregateRoot<EventStoreFactory> {
|
||||
static override readonly name = "organization";
|
||||
|
||||
id!: string;
|
||||
|
||||
name!: string;
|
||||
|
||||
createdAt!: Date;
|
||||
updatedAt!: Date;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Factories
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static #reducer = makeAggregateReducer(Organization);
|
||||
|
||||
static create(name: string, meta: Auditor): Organization {
|
||||
return new Organization().push({
|
||||
type: "organization:created",
|
||||
data: { name },
|
||||
meta,
|
||||
});
|
||||
}
|
||||
|
||||
static async getById(stream: string): Promise<Organization | undefined> {
|
||||
return this.$store.reduce({ name: "organization", stream, reducer: this.#reducer });
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Reducer
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
with(event: EventStoreFactory["$events"][number]["$record"]): void {
|
||||
switch (event.type) {
|
||||
case "organization:created": {
|
||||
this.id = event.stream;
|
||||
this.name = event.data.name;
|
||||
this.createdAt = getDate(event.created);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------------
|
||||
| Projectors
|
||||
|--------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
projector.on("organization:created", async ({ stream: id, data: { name }, created }) => {
|
||||
await db.collection("organizations").insertOne({
|
||||
id,
|
||||
name,
|
||||
createdAt: getDate(created),
|
||||
});
|
||||
});
|
||||
@@ -1,118 +0,0 @@
|
||||
import { AggregateRoot, getDate, makeAggregateReducer } from "@valkyr/event-store";
|
||||
|
||||
import { db } from "~libraries/read-store/database.ts";
|
||||
|
||||
import type { Auditor } from "../events/auditor.ts";
|
||||
import { EventStoreFactory } from "../events/mod.ts";
|
||||
import type { RoleCreatedData, RolePermissionOperation } from "../events/role.ts";
|
||||
import { projector } from "../projector.ts";
|
||||
|
||||
export class Role extends AggregateRoot<EventStoreFactory> {
|
||||
static override readonly name = "role";
|
||||
|
||||
id!: string;
|
||||
|
||||
name!: string;
|
||||
permissions: { [resource: string]: Set<string> } = {};
|
||||
|
||||
createdAt!: Date;
|
||||
updatedAt!: Date;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Factories
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static #reducer = makeAggregateReducer(Role);
|
||||
|
||||
static create(data: RoleCreatedData, meta: Auditor): Role {
|
||||
return new Role().push({
|
||||
type: "role:created",
|
||||
data,
|
||||
meta,
|
||||
});
|
||||
}
|
||||
|
||||
static async getById(stream: string): Promise<Role | undefined> {
|
||||
return this.$store.reduce({ name: "role", stream, reducer: this.#reducer });
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Reducer
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
override with(event: EventStoreFactory["$events"][number]["$record"]): void {
|
||||
switch (event.type) {
|
||||
case "role:created": {
|
||||
this.id = event.stream;
|
||||
this.createdAt = getDate(event.created);
|
||||
this.updatedAt = getDate(event.created);
|
||||
break;
|
||||
}
|
||||
case "role:name-set": {
|
||||
this.name = event.data;
|
||||
this.updatedAt = getDate(event.created);
|
||||
break;
|
||||
}
|
||||
case "role:permissions-set": {
|
||||
for (const operation of event.data) {
|
||||
if (operation.type === "grant") {
|
||||
if (this.permissions[operation.resource] === undefined) {
|
||||
this.permissions[operation.resource] = new Set();
|
||||
}
|
||||
this.permissions[operation.resource].add(operation.action);
|
||||
}
|
||||
if (operation.type === "deny") {
|
||||
if (operation.action === undefined) {
|
||||
delete this.permissions[operation.resource];
|
||||
} else {
|
||||
this.permissions[operation.resource]?.delete(operation.action);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Actions
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
setName(name: string, meta: Auditor): this {
|
||||
return this.push({
|
||||
type: "role:name-set",
|
||||
stream: this.id,
|
||||
data: name,
|
||||
meta,
|
||||
});
|
||||
}
|
||||
|
||||
setPermissions(operations: RolePermissionOperation[], meta: Auditor): this {
|
||||
return this.push({
|
||||
type: "role:permissions-set",
|
||||
stream: this.id,
|
||||
data: operations,
|
||||
meta,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------------
|
||||
| Projectors
|
||||
|--------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
projector.on("role:created", async ({ stream, data: { name, permissions } }) => {
|
||||
await db.collection("roles").insertOne({
|
||||
id: stream,
|
||||
name,
|
||||
permissions: permissions.reduce(
|
||||
(map, permission) => {
|
||||
map[permission.resource] = permission.actions;
|
||||
return map;
|
||||
},
|
||||
{} as Record<string, string[]>,
|
||||
),
|
||||
});
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
import { EventStore } from "@valkyr/event-store";
|
||||
import { MongoAdapter } from "@valkyr/event-store/mongo";
|
||||
|
||||
import { container } from "~libraries/database/container.ts";
|
||||
|
||||
import { aggregates } from "./aggregates/mod.ts";
|
||||
import { events } from "./events/mod.ts";
|
||||
import { projector } from "./projector.ts";
|
||||
|
||||
export const eventStore = new EventStore({
|
||||
adapter: new MongoAdapter(() => container.get("client"), "balto:event-store"),
|
||||
events,
|
||||
aggregates,
|
||||
snapshot: "auto",
|
||||
});
|
||||
|
||||
eventStore.onEventsInserted(async (records, { batch }) => {
|
||||
if (batch !== undefined) {
|
||||
await projector.pushMany(batch, records);
|
||||
} else {
|
||||
for (const record of records) {
|
||||
await projector.push(record, { hydrated: false, outdated: false });
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
import { EmailSchema, NameSchema } from "@spec/shared";
|
||||
import { event } from "@valkyr/event-store";
|
||||
import z from "zod";
|
||||
|
||||
import { auditor } from "./auditor.ts";
|
||||
|
||||
export default [
|
||||
event.type("account:avatar:added").data(z.string()).meta(auditor),
|
||||
event.type("account:name:added").data(NameSchema).meta(auditor),
|
||||
event.type("account:email:added").data(EmailSchema).meta(auditor),
|
||||
event.type("account:role:added").data(z.string()).meta(auditor),
|
||||
];
|
||||
@@ -1,7 +0,0 @@
|
||||
import z from "zod";
|
||||
|
||||
export const auditor = z.object({
|
||||
accountId: z.string(),
|
||||
});
|
||||
|
||||
export type Auditor = z.infer<typeof auditor>;
|
||||
@@ -1,30 +0,0 @@
|
||||
import { event } from "@valkyr/event-store";
|
||||
import z from "zod";
|
||||
|
||||
const identity = z.discriminatedUnion([
|
||||
z.object({
|
||||
type: z.literal("admin"),
|
||||
accountId: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal("consultant"),
|
||||
accountId: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal("organization"),
|
||||
organizationId: z.string(),
|
||||
accountId: z.string(),
|
||||
}),
|
||||
]);
|
||||
|
||||
export default [
|
||||
event.type("code:created").data(
|
||||
z.object({
|
||||
value: z.string(),
|
||||
identity,
|
||||
}),
|
||||
),
|
||||
event.type("code:claimed"),
|
||||
];
|
||||
|
||||
export type CodeIdentity = z.infer<typeof identity>;
|
||||
@@ -1,11 +0,0 @@
|
||||
import { EventFactory } from "@valkyr/event-store";
|
||||
|
||||
import account from "./account.ts";
|
||||
import code from "./code.ts";
|
||||
import organization from "./organization.ts";
|
||||
import role from "./role.ts";
|
||||
import strategy from "./strategy.ts";
|
||||
|
||||
export const events = new EventFactory([...account, ...code, ...organization, ...role, ...strategy]);
|
||||
|
||||
export type EventStoreFactory = typeof events;
|
||||
@@ -1,11 +0,0 @@
|
||||
import { event } from "@valkyr/event-store";
|
||||
import z from "zod";
|
||||
|
||||
import { auditor } from "./auditor.ts";
|
||||
|
||||
export default [
|
||||
event
|
||||
.type("organization:created")
|
||||
.data(z.object({ name: z.string() }))
|
||||
.meta(auditor),
|
||||
];
|
||||
@@ -1,37 +0,0 @@
|
||||
import { event } from "@valkyr/event-store";
|
||||
import z from "zod";
|
||||
|
||||
import { auditor } from "./auditor.ts";
|
||||
|
||||
const CreatedSchema = z.object({
|
||||
name: z.string(),
|
||||
permissions: z.array(
|
||||
z.object({
|
||||
resource: z.string(),
|
||||
actions: z.array(z.string()),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
const OperationSchema = z.discriminatedUnion("type", [
|
||||
z.object({
|
||||
type: z.literal("grant"),
|
||||
resource: z.string(),
|
||||
action: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal("deny"),
|
||||
resource: z.string(),
|
||||
action: z.string().optional(),
|
||||
}),
|
||||
]);
|
||||
|
||||
export default [
|
||||
event.type("role:created").data(CreatedSchema).meta(auditor),
|
||||
event.type("role:name-set").data(z.string()).meta(auditor),
|
||||
event.type("role:permissions-set").data(z.array(OperationSchema)).meta(auditor),
|
||||
];
|
||||
|
||||
export type RoleCreatedData = z.infer<typeof CreatedSchema>;
|
||||
|
||||
export type RolePermissionOperation = z.infer<typeof OperationSchema>;
|
||||
@@ -1,13 +0,0 @@
|
||||
import { event } from "@valkyr/event-store";
|
||||
import z from "zod";
|
||||
|
||||
import { auditor } from "./auditor.ts";
|
||||
|
||||
export default [
|
||||
event.type("strategy:email:added").data(z.string()).meta(auditor),
|
||||
event.type("strategy:passkey:added").meta(auditor),
|
||||
event
|
||||
.type("strategy:password:added")
|
||||
.data(z.object({ alias: z.string(), password: z.string() }))
|
||||
.meta(auditor),
|
||||
];
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from "./event-store.ts";
|
||||
export * from "./projector.ts";
|
||||
@@ -1,5 +0,0 @@
|
||||
import { Projector } from "@valkyr/event-store";
|
||||
|
||||
import { EventStoreFactory } from "./events/mod.ts";
|
||||
|
||||
export const projector = new Projector<EventStoreFactory>();
|
||||
@@ -1,11 +0,0 @@
|
||||
import { idIndex } from "~libraries/database/id.ts";
|
||||
import { register } from "~libraries/database/registrar.ts";
|
||||
|
||||
import { db } from "../database.ts";
|
||||
|
||||
await register(db.db, [
|
||||
{
|
||||
name: "accounts",
|
||||
indexes: [idIndex],
|
||||
},
|
||||
]);
|
||||
@@ -1,12 +0,0 @@
|
||||
import type { AccountDocument } from "@spec/modules/account/account.ts";
|
||||
|
||||
import { config } from "~config";
|
||||
import { getDatabaseAccessor } from "~libraries/database/accessor.ts";
|
||||
|
||||
export const db = getDatabaseAccessor<{
|
||||
accounts: AccountDocument;
|
||||
}>(`${config.name}:read-store`);
|
||||
|
||||
export function takeOne<TDocument>(documents: TDocument[]): TDocument | undefined {
|
||||
return documents[0];
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import { type Account, parseAccount } from "@spec/modules/account/account.ts";
|
||||
|
||||
import { db, takeOne } from "./database.ts";
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------------
|
||||
| Accounts
|
||||
|--------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Retrieve a single account by its primary identifier.
|
||||
*
|
||||
* @param id - Account identifier.
|
||||
*/
|
||||
export async function getAccountById(id: string): Promise<Account | undefined> {
|
||||
return db
|
||||
.collection("accounts")
|
||||
.aggregate([
|
||||
{
|
||||
$match: { id },
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: "roles",
|
||||
localField: "roles",
|
||||
foreignField: "id",
|
||||
as: "roles",
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray()
|
||||
.then(parseAccount)
|
||||
.then(takeOne);
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from "./database.ts";
|
||||
export * from "./methods.ts";
|
||||
@@ -1,16 +1,41 @@
|
||||
import { RouteContext } from "@spec/relay";
|
||||
import { ServerContext, UnauthorizedError } from "@spec/relay";
|
||||
|
||||
export function getRequestContext(request: Request): RouteContext {
|
||||
return {
|
||||
request,
|
||||
};
|
||||
}
|
||||
import { Session } from "../auth/auth.ts";
|
||||
import { req } from "./request.ts";
|
||||
|
||||
declare module "@spec/relay" {
|
||||
interface RouteContext {
|
||||
interface ServerContext {
|
||||
/**
|
||||
* Current request instance being handled.
|
||||
*/
|
||||
request: Request;
|
||||
|
||||
/**
|
||||
* Get request session instance.
|
||||
*/
|
||||
session: Session;
|
||||
|
||||
/**
|
||||
* Get account id from session, throws an error if the request
|
||||
* does not have a valid session.
|
||||
*/
|
||||
accountId: string;
|
||||
}
|
||||
}
|
||||
|
||||
export function getRequestContext(request: Request): ServerContext {
|
||||
return {
|
||||
request,
|
||||
|
||||
get session(): Session {
|
||||
if (req.session === undefined) {
|
||||
throw new UnauthorizedError();
|
||||
}
|
||||
return req.session;
|
||||
},
|
||||
|
||||
get accountId() {
|
||||
return this.session.accountId;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Route } from "@spec/relay";
|
||||
export async function resolveRoutes(path: string, routes: Route[] = []): Promise<Route[]> {
|
||||
for await (const entry of Deno.readDir(path)) {
|
||||
if (entry.isDirectory === true) {
|
||||
await loadRoutes(`${path}/${entry.name}/routes`, routes, [name]);
|
||||
await loadRoutes(`${path}/${entry.name}`, routes, [name]);
|
||||
}
|
||||
}
|
||||
return routes;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Session } from "../auth/auth.ts";
|
||||
import { asyncLocalStorage } from "./storage.ts";
|
||||
|
||||
export const req = {
|
||||
@@ -24,14 +25,14 @@ export const req = {
|
||||
/**
|
||||
* Check if the request is authenticated.
|
||||
*/
|
||||
get isAuthenticated() {
|
||||
get isAuthenticated(): boolean {
|
||||
return this.session !== undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get current session.
|
||||
*/
|
||||
get session() {
|
||||
get session(): Session | undefined {
|
||||
return this.store.session;
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user