@cohabit/resources-manager@0.2.1
latest
Système de gestion des ressources de cohabit.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155import type { Base64String, ToJson, UUID } from '../../../types.ts' import { Avatar } from '../utils/avatar.ts' import type { Ref } from '../utils/ref.ts' import { Resource, type ResourceJson } from './resource.ts' export class Credential<T extends CredentialCategory> extends Resource { static fromJSON<T extends CredentialCategory>( json: ToJson<Credential<T>>, ): Credential<T> { return new Credential(json) } static create<T extends CredentialCategory>( { category, store, name, avatar }: Pick< Credential<T>, 'category' | 'store' | 'name' | 'avatar' >, ): Credential<T> { const { uuid, createdAt, updatedAt } = super.create({ name, avatar }) return new Credential({ uuid, createdAt, updatedAt, name, category, store, avatar, }) } static load<T extends CredentialCategory>({ category, store, name, avatar = Avatar.fromEmoji('🔑'), }: { name: Credential<T>['name'] category: T store: CredentialStore<T>['store'] avatar?: Credential<T>['avatar'] }): Credential<T> { return this.create({ category, store, name, avatar }) } #category: T #store: Readonly<CredentialStore<T>['store']> private constructor( { category, store, ...props }: & Pick<Credential<T>, 'category' | 'store'> & Pick<Resource, 'name' | 'uuid' | 'avatar' | 'createdAt' | 'updatedAt'>, ) { super(props) if (!['password', 'ssh', 'passkey'].includes(category)) { throw new TypeError( `category is "${category}" but ('password' | 'ssh' | 'passkey') is required`, ) } this.#category = category this.#store = Object.freeze(store) } get type(): 'credential' { return 'credential' } get category(): T { return this.#category } get store(): Readonly<CredentialStore<T>['store']> { return this.#store } update( props: Partial<Omit<Credential<T>, 'type' | 'uuid' | 'createdAt'>>, ): Credential<T> { const { updatedAt } = super.update(props) return new Credential<T>({ uuid: this.uuid, name: this.name, avatar: this.avatar, createdAt: this.createdAt, category: this.category, store: this.store, ...props, updatedAt, }) } toJSON(): ResourceJson<Credential<T>, 'category' | 'store'> { return { ...super.toJSON(), type: this.type, category: this.category, store: Object.freeze(this.store), } as const } toString(): string { return `Credential (${JSON.stringify(this)})` } toRef(): Ref<Credential<T>> { return super.toRef() as Ref<Credential<T>> } } export interface Credential<T extends CredentialCategory> extends Resource { type: 'credential' category: T store: Readonly<CredentialStore<T>['store']> } export type CredentialCategory = 'password' | 'ssh' | 'passkey' export type CredentialStore<T extends CredentialCategory> = T extends 'password' ? { store: { hash: Base64String alg: string salt: Base64String } } : T extends 'ssh' ? { store: { publicKey: Base64String } } : T extends 'passkey' ? { store: Passkey } : never /** Passkey store */ export type Passkey = { /** User UUID */ user: UUID /** WebAuthn registration key id */ webAuthnUserID: string /** Passkey credential unique id */ id: string /** Passkey user public key */ publicKey: Base64String /** Number of times the authenticator has been used */ counter: number /** Whether the passkey is single-device or multi-device */ deviceType: 'singleDevice' | 'multiDevice' /** Whether the passkey has been backed up in some way */ backedUp: boolean /** Passkey physical transport layer */ transports?: ('ble' | 'cable' | 'hybrid' | 'internal' | 'nfc' | 'smart-card' | 'usb')[] }