mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-12 16:55:37 +02:00
⭐ feat: New account system with improved team management (#273)
Description <!-- Briefly describe the purpose and scope of your changes --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced comprehensive account management with combined user and team info. - Added advanced, context-aware logging utilities. - Implemented invite code generation for teams with uniqueness guarantees. - Expanded example data for users, teams, subscriptions, sessions, and games. - **Enhancements** - Refined user, team, member, and Steam account schemas for richer data and validation. - Streamlined user creation, login acknowledgment, and error handling. - Improved API authentication and unified actor context management. - Added persistent shared temporary volume support to API and auth services. - Enhanced Steam account management with create, update, and event notifications. - Refined team listing and serialization integrating Steam accounts as members. - Simplified event, context, and logging systems. - Updated API and auth middleware for better token handling and actor provisioning. - **Bug Fixes** - Fixed multiline log output to prefix each line with log level. - **Removals** - Removed machine and subscription management features, including schemas and DB tables. - Disabled machine-based authentication and removed related subject schemas. - Removed deprecated fields and legacy logic from member and team management. - Removed legacy event and error handling related to teams and members. - **Chores** - Reorganized and cleaned exports across utility and API modules. - Updated database schemas for users, teams, members, and Steam accounts. - Improved internal code structure, imports, and error messaging. - Moved logger patching to earlier initialization for consistent logging. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1,142 +1,129 @@
|
||||
import { z } from "zod";
|
||||
import { eq } from "./drizzle";
|
||||
import { ErrorCodes, VisibleError } from "./error";
|
||||
import { Log } from "./utils";
|
||||
import { createContext } from "./context";
|
||||
import { UserFlags, userTable } from "./user/user.sql";
|
||||
import { useTransaction } from "./drizzle/transaction";
|
||||
import { ErrorCodes, VisibleError } from "./error";
|
||||
|
||||
export const PublicActor = z.object({
|
||||
type: z.literal("public"),
|
||||
properties: z.object({}),
|
||||
});
|
||||
export type PublicActor = z.infer<typeof PublicActor>;
|
||||
|
||||
export const UserActor = z.object({
|
||||
type: z.literal("user"),
|
||||
properties: z.object({
|
||||
userID: z.string(),
|
||||
email: z.string().nonempty(),
|
||||
}),
|
||||
});
|
||||
export type UserActor = z.infer<typeof UserActor>;
|
||||
|
||||
export const MemberActor = z.object({
|
||||
type: z.literal("member"),
|
||||
properties: z.object({
|
||||
memberID: z.string(),
|
||||
teamID: z.string(),
|
||||
}),
|
||||
});
|
||||
export type MemberActor = z.infer<typeof MemberActor>;
|
||||
|
||||
export const SystemActor = z.object({
|
||||
type: z.literal("system"),
|
||||
properties: z.object({
|
||||
teamID: z.string(),
|
||||
}),
|
||||
});
|
||||
export type SystemActor = z.infer<typeof SystemActor>;
|
||||
|
||||
export const MachineActor = z.object({
|
||||
type: z.literal("machine"),
|
||||
properties: z.object({
|
||||
fingerprint: z.string(),
|
||||
machineID: z.string(),
|
||||
}),
|
||||
});
|
||||
export type MachineActor = z.infer<typeof MachineActor>;
|
||||
|
||||
export const Actor = z.discriminatedUnion("type", [
|
||||
MemberActor,
|
||||
UserActor,
|
||||
PublicActor,
|
||||
SystemActor,
|
||||
MachineActor
|
||||
]);
|
||||
export type Actor = z.infer<typeof Actor>;
|
||||
|
||||
export const ActorContext = createContext<Actor>("actor");
|
||||
|
||||
export const useActor = ActorContext.use;
|
||||
export const withActor = ActorContext.with;
|
||||
|
||||
/**
|
||||
* Retrieves the user ID of the current actor.
|
||||
*
|
||||
* This function accesses the actor context and returns the `userID` if the current
|
||||
* actor is of type "user". If the actor is not a user, it throws a `VisibleError`
|
||||
* with an authentication error code, indicating that the caller is not authorized
|
||||
* to access user-specific resources.
|
||||
*
|
||||
* @throws {VisibleError} When the current actor is not of type "user".
|
||||
*/
|
||||
export function useUserID() {
|
||||
const actor = ActorContext.use();
|
||||
if (actor.type === "user") return actor.properties.userID;
|
||||
throw new VisibleError(
|
||||
"authentication",
|
||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
||||
`You don't have permission to access this resource`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the properties of the current user actor.
|
||||
*
|
||||
* This function obtains the current actor from the context and returns its properties if the actor is identified as a user.
|
||||
* If the actor is not of type "user", it throws a {@link VisibleError} with an authentication error code,
|
||||
* indicating that the user is not authorized to access user-specific resources.
|
||||
*
|
||||
* @returns The properties of the current user actor, typically including user-specific details such as userID and email.
|
||||
* @throws {VisibleError} If the current actor is not a user.
|
||||
*/
|
||||
export function useUser() {
|
||||
const actor = ActorContext.use();
|
||||
if (actor.type === "user") return actor.properties;
|
||||
throw new VisibleError(
|
||||
"authentication",
|
||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
||||
`You don't have permission to access this resource`,
|
||||
);
|
||||
}
|
||||
|
||||
export function assertActor<T extends Actor["type"]>(type: T) {
|
||||
const actor = useActor();
|
||||
if (actor.type !== type) {
|
||||
throw new Error(`Expected actor type ${type}, got ${actor.type}`);
|
||||
export namespace Actor {
|
||||
|
||||
export interface User {
|
||||
type: "user";
|
||||
properties: {
|
||||
userID: string;
|
||||
email: string;
|
||||
};
|
||||
}
|
||||
|
||||
return actor as Extract<Actor, { type: T }>;
|
||||
}
|
||||
export interface System {
|
||||
type: "system";
|
||||
properties: {
|
||||
teamID: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current actor's team ID.
|
||||
*
|
||||
* @returns The team ID associated with the current actor.
|
||||
* @throws {VisibleError} If the current actor does not have a {@link teamID} property.
|
||||
*/
|
||||
export function useTeam() {
|
||||
const actor = useActor();
|
||||
if ("teamID" in actor.properties) return actor.properties.teamID;
|
||||
throw new VisibleError(
|
||||
"authentication",
|
||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
||||
`Expected actor to have teamID`
|
||||
);
|
||||
}
|
||||
export interface Machine {
|
||||
type: "machine";
|
||||
properties: {
|
||||
machineID: string;
|
||||
fingerprint: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fingerprint of the current actor if the actor has a machine identity.
|
||||
*
|
||||
* @returns The fingerprint of the current machine actor.
|
||||
* @throws {VisibleError} If the current actor does not have a machine identity.
|
||||
*/
|
||||
export function useMachine() {
|
||||
const actor = useActor();
|
||||
if ("machineID" in actor.properties) return actor.properties.fingerprint;
|
||||
throw new VisibleError(
|
||||
"authentication",
|
||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
||||
`Expected actor to have fingerprint`
|
||||
);
|
||||
export interface Token {
|
||||
type: "steam";
|
||||
properties: {
|
||||
steamID: bigint;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Public {
|
||||
type: "public";
|
||||
properties: {};
|
||||
}
|
||||
|
||||
export type Info = User | Public | Token | System | Machine;
|
||||
|
||||
export const Context = createContext<Info>();
|
||||
|
||||
export function userID() {
|
||||
const actor = Context.use();
|
||||
if ("userID" in actor.properties) return actor.properties.userID;
|
||||
throw new VisibleError(
|
||||
"authentication",
|
||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
||||
`You don't have permission to access this resource.`,
|
||||
);
|
||||
}
|
||||
|
||||
export function steamID() {
|
||||
const actor = Context.use();
|
||||
if ("steamID" in actor.properties) return actor.properties.steamID;
|
||||
throw new VisibleError(
|
||||
"authentication",
|
||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
||||
`You don't have permission to access this resource.`,
|
||||
);
|
||||
}
|
||||
|
||||
export function user() {
|
||||
const actor = Context.use();
|
||||
if (actor.type == "user") return actor.properties;
|
||||
throw new VisibleError(
|
||||
"authentication",
|
||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
||||
`You don't have permission to access this resource.`,
|
||||
);
|
||||
}
|
||||
|
||||
export function teamID() {
|
||||
const actor = Context.use();
|
||||
if ("teamID" in actor.properties) return actor.properties.teamID;
|
||||
throw new VisibleError(
|
||||
"authentication",
|
||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
||||
`You don't have permission to access this resource.`,
|
||||
);
|
||||
}
|
||||
|
||||
export function fingerprint() {
|
||||
const actor = Context.use();
|
||||
if ("fingerprint" in actor.properties) return actor.properties.fingerprint;
|
||||
throw new VisibleError(
|
||||
"authentication",
|
||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
||||
`You don't have permission to access this resource.`,
|
||||
);
|
||||
}
|
||||
|
||||
export function use() {
|
||||
try {
|
||||
return Context.use();
|
||||
} catch {
|
||||
return { type: "public", properties: {} } as Public;
|
||||
}
|
||||
}
|
||||
|
||||
export function assert<T extends Info["type"]>(type: T) {
|
||||
const actor = use();
|
||||
if (actor.type !== type)
|
||||
throw new VisibleError(
|
||||
"authentication",
|
||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
||||
`Actor is not "${type}"`,
|
||||
);
|
||||
return actor as Extract<Info, { type: T }>;
|
||||
}
|
||||
|
||||
export function provide<
|
||||
T extends Info["type"],
|
||||
Next extends (...args: any) => any,
|
||||
>(type: T, properties: Extract<Info, { type: T }>["properties"], fn: Next) {
|
||||
return Context.provide({ type, properties } as any, () =>
|
||||
Log.provide(
|
||||
{
|
||||
actor: type,
|
||||
...properties,
|
||||
},
|
||||
fn,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user