import { AuthAdministrativeScopes, AuthStandardClientScopes, type AuthEnvironmentScope, type AuthPairingLink, type ServerAuthBootstrapMethod,} from "@t3tools/contracts";import * as Context from "effect/Context";import * as Crypto from "effect/Crypto";import * as DateTime from "effect/DateTime";import * as Duration from "effect/Duration";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as PubSub from "effect/PubSub";import * as Ref from "effect/Ref";import * as Schema from "effect/Schema";import * as Stream from "effect/Stream"; import * as ServerConfig from "../config.ts";import * as AuthPairingLinks from "../persistence/AuthPairingLinks.ts"; export interface BootstrapGrant { readonly method: ServerAuthBootstrapMethod; readonly scopes: ReadonlyArray<AuthEnvironmentScope>; readonly subject: string; readonly label?: string; readonly proofKeyThumbprint?: string; readonly expiresAt: DateTime.DateTime;} export class UnknownBootstrapCredentialError extends Schema.TaggedErrorClass<UnknownBootstrapCredentialError>()( "UnknownBootstrapCredentialError", {},) { override get message(): string { return "Unknown bootstrap credential."; }} export class ExpiredBootstrapCredentialError extends Schema.TaggedErrorClass<ExpiredBootstrapCredentialError>()( "ExpiredBootstrapCredentialError", {},) { override get message(): string { return "Bootstrap credential expired."; }} export class BootstrapCredentialProofKeyMismatchError extends Schema.TaggedErrorClass<BootstrapCredentialProofKeyMismatchError>()( "BootstrapCredentialProofKeyMismatchError", {},) { override get message(): string { return "Bootstrap credential proof key mismatch."; }} export class UnavailableBootstrapCredentialError extends Schema.TaggedErrorClass<UnavailableBootstrapCredentialError>()( "UnavailableBootstrapCredentialError", {},) { override get message(): string { return "Bootstrap credential is no longer available."; }} export const BootstrapCredentialInvalidError = Schema.Union([ UnknownBootstrapCredentialError, ExpiredBootstrapCredentialError, BootstrapCredentialProofKeyMismatchError, UnavailableBootstrapCredentialError,]);export type BootstrapCredentialInvalidError = typeof BootstrapCredentialInvalidError.Type;export const isBootstrapCredentialInvalidError = Schema.is(BootstrapCredentialInvalidError); export class ActivePairingLinksLoadError extends Schema.TaggedErrorClass<ActivePairingLinksLoadError>()( "ActivePairingLinksLoadError", { cause: Schema.Defect(), },) { override get message(): string { return "Failed to load active pairing links."; }} export class PairingLinkRevokeError extends Schema.TaggedErrorClass<PairingLinkRevokeError>()( "PairingLinkRevokeError", { cause: Schema.Defect(), },) { override get message(): string { return "Failed to revoke pairing link."; }} export class PairingCredentialIssueError extends Schema.TaggedErrorClass<PairingCredentialIssueError>()( "PairingCredentialIssueError", { cause: Schema.Defect(), },) { override get message(): string { return "Failed to issue pairing credential."; }} export class BootstrapCredentialConsumeError extends Schema.TaggedErrorClass<BootstrapCredentialConsumeError>()( "BootstrapCredentialConsumeError", { cause: Schema.Defect(), },) { override get message(): string { return "Failed to consume bootstrap credential."; }} export const BootstrapCredentialInternalError = Schema.Union([ ActivePairingLinksLoadError, PairingLinkRevokeError, PairingCredentialIssueError, BootstrapCredentialConsumeError,]);export type BootstrapCredentialInternalError = typeof BootstrapCredentialInternalError.Type;export const isBootstrapCredentialInternalError = Schema.is(BootstrapCredentialInternalError); export const BootstrapCredentialError = Schema.Union([ BootstrapCredentialInvalidError, BootstrapCredentialInternalError,]);export type BootstrapCredentialError = typeof BootstrapCredentialError.Type;export const isBootstrapCredentialError = Schema.is(BootstrapCredentialError); export interface IssuedBootstrapCredential { readonly id: string; readonly credential: string; readonly label?: string; readonly proofKeyThumbprint?: string; readonly expiresAt: DateTime.Utc;} export type BootstrapCredentialChange = | { readonly type: "pairingLinkUpserted"; readonly pairingLink: AuthPairingLink; } | { readonly type: "pairingLinkRemoved"; readonly id: string; }; export class PairingGrantStore extends Context.Service< PairingGrantStore, { readonly issueOneTimeToken: (input?: { readonly ttl?: Duration.Duration; readonly scopes?: ReadonlyArray<AuthEnvironmentScope>; readonly subject?: string; readonly label?: string; readonly proofKeyThumbprint?: string; }) => Effect.Effect<IssuedBootstrapCredential, BootstrapCredentialInternalError>; readonly listActive: () => Effect.Effect< ReadonlyArray<AuthPairingLink>, BootstrapCredentialInternalError >; readonly streamChanges: Stream.Stream<BootstrapCredentialChange>; readonly revoke: (id: string) => Effect.Effect<boolean, BootstrapCredentialInternalError>; readonly consume: ( credential: string, input?: { readonly proofKeyThumbprint?: string; }, ) => Effect.Effect<BootstrapGrant, BootstrapCredentialError>; }>()("t3/auth/PairingGrantStore") {} interface StoredBootstrapGrant extends BootstrapGrant { readonly remainingUses: number | "unbounded";} type ConsumeResult = | { readonly _tag: "error"; readonly reason: "not-found" | "expired"; readonly error: BootstrapCredentialError; } | { readonly _tag: "success"; readonly grant: BootstrapGrant; }; const DEFAULT_ONE_TIME_TOKEN_TTL_MINUTES = Duration.minutes(5);const PAIRING_TOKEN_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";const PAIRING_TOKEN_LENGTH = 12;const PAIRING_TOKEN_REJECTION_LIMIT = Math.floor(256 / PAIRING_TOKEN_ALPHABET.length) * PAIRING_TOKEN_ALPHABET.length; export const make = Effect.gen(function* () { const crypto = yield* Crypto.Crypto; const config = yield* ServerConfig.ServerConfig; const pairingLinks = yield* AuthPairingLinks.AuthPairingLinkRepository; const seededGrantsRef = yield* Ref.make(new Map<string, StoredBootstrapGrant>()); const changesPubSub = yield* PubSub.unbounded<BootstrapCredentialChange>(); const generatePairingToken = Effect.gen(function* () { let credential = ""; while (credential.length < PAIRING_TOKEN_LENGTH) { const bytes = yield* crypto.randomBytes(PAIRING_TOKEN_LENGTH); for (const byte of bytes) { if (byte >= PAIRING_TOKEN_REJECTION_LIMIT) { continue; } credential += PAIRING_TOKEN_ALPHABET[byte % PAIRING_TOKEN_ALPHABET.length]!; if (credential.length === PAIRING_TOKEN_LENGTH) { return credential; } } } return credential; }); const seedGrant = (credential: string, grant: StoredBootstrapGrant) => Ref.update(seededGrantsRef, (current) => { const next = new Map(current); next.set(credential, grant); return next; }); const emitUpsert = (pairingLink: AuthPairingLink) => PubSub.publish(changesPubSub, { type: "pairingLinkUpserted", pairingLink, }).pipe(Effect.asVoid); const emitRemoved = (id: string) => PubSub.publish(changesPubSub, { type: "pairingLinkRemoved", id, }).pipe(Effect.asVoid); if (config.desktopBootstrapToken) { const now = yield* DateTime.now; yield* seedGrant(config.desktopBootstrapToken, { method: "desktop-bootstrap", scopes: AuthAdministrativeScopes, subject: "desktop-bootstrap", expiresAt: DateTime.add(now, { milliseconds: Duration.toMillis(DEFAULT_ONE_TIME_TOKEN_TTL_MINUTES), }), remainingUses: 1, }); } const listActive: PairingGrantStore["Service"]["listActive"] = Effect.fn( "PairingGrantStore.listActive", )( function* () { const now = yield* DateTime.now; const rows = yield* pairingLinks.listActive({ now }); return rows.map((row) => row.label ? ({ id: row.id, credential: row.credential, scopes: row.scopes, subject: row.subject, label: row.label, createdAt: row.createdAt, expiresAt: row.expiresAt, } satisfies AuthPairingLink) : ({ id: row.id, credential: row.credential, scopes: row.scopes, subject: row.subject, createdAt: row.createdAt, expiresAt: row.expiresAt, } satisfies AuthPairingLink), ); }, Effect.mapError((cause) => new ActivePairingLinksLoadError({ cause })), ); const revoke: PairingGrantStore["Service"]["revoke"] = Effect.fn("PairingGrantStore.revoke")( function* (id) { const revokedAt = yield* DateTime.now; const revoked = yield* pairingLinks.revoke({ id, revokedAt, }); if (revoked) { yield* emitRemoved(id); } return revoked; }, Effect.mapError((cause) => new PairingLinkRevokeError({ cause })), ); const issueOneTimeToken: PairingGrantStore["Service"]["issueOneTimeToken"] = Effect.fn( "PairingGrantStore.issueOneTimeToken", )( function* (input) { const id = yield* crypto.randomUUIDv4; const credential = yield* generatePairingToken; const ttl = input?.ttl ?? DEFAULT_ONE_TIME_TOKEN_TTL_MINUTES; const now = yield* DateTime.now; const expiresAt = DateTime.add(now, { milliseconds: Duration.toMillis(ttl) }); const issued: IssuedBootstrapCredential = { id, credential, ...(input?.label ? { label: input.label } : {}), ...(input?.proofKeyThumbprint ? { proofKeyThumbprint: input.proofKeyThumbprint } : {}), expiresAt, }; yield* pairingLinks.create({ id, credential, method: "one-time-token", scopes: input?.scopes ?? AuthStandardClientScopes, subject: input?.subject ?? "one-time-token", label: input?.label ?? null, proofKeyThumbprint: input?.proofKeyThumbprint ?? null, createdAt: now, expiresAt: expiresAt, }); yield* emitUpsert({ id, credential, scopes: input?.scopes ?? AuthStandardClientScopes, subject: input?.subject ?? "one-time-token", ...(input?.label ? { label: input.label } : {}), createdAt: now, expiresAt, }); return issued; }, Effect.mapError((cause) => new PairingCredentialIssueError({ cause })), ); const consume: PairingGrantStore["Service"]["consume"] = Effect.fn("PairingGrantStore.consume")( function* (credential, input) { const now = yield* DateTime.now; const seededResult: ConsumeResult = yield* Ref.modify( seededGrantsRef, (current): readonly [ConsumeResult, Map<string, StoredBootstrapGrant>] => { const grant = current.get(credential); if (!grant) { return [ { _tag: "error", reason: "not-found", error: new UnknownBootstrapCredentialError({}), }, current, ]; } const next = new Map(current); if (DateTime.isGreaterThanOrEqualTo(now, grant.expiresAt)) { next.delete(credential); return [ { _tag: "error", reason: "expired", error: new ExpiredBootstrapCredentialError({}), }, next, ]; } if (grant.proofKeyThumbprint && grant.proofKeyThumbprint !== input?.proofKeyThumbprint) { return [ { _tag: "error", reason: "not-found", error: new BootstrapCredentialProofKeyMismatchError({}), }, next, ]; } const remainingUses = grant.remainingUses; if (typeof remainingUses === "number") { if (remainingUses <= 1) { next.delete(credential); } else { next.set(credential, { ...grant, remainingUses: remainingUses - 1, }); } } return [ { _tag: "success", grant: { method: grant.method, scopes: grant.scopes, subject: grant.subject, ...(grant.label ? { label: grant.label } : {}), ...(grant.proofKeyThumbprint ? { proofKeyThumbprint: grant.proofKeyThumbprint } : {}), expiresAt: grant.expiresAt, } satisfies BootstrapGrant, }, next, ]; }, ); if (seededResult._tag === "success") { return seededResult.grant; } if (seededResult.reason !== "not-found") { return yield* seededResult.error; } const consumed = yield* pairingLinks.consumeAvailable({ credential, proofKeyThumbprint: input?.proofKeyThumbprint ?? null, consumedAt: now, now, }); if (Option.isSome(consumed)) { yield* emitRemoved(consumed.value.id); return { method: consumed.value.method, scopes: consumed.value.scopes, subject: consumed.value.subject, ...(consumed.value.label ? { label: consumed.value.label } : {}), ...(consumed.value.proofKeyThumbprint ? { proofKeyThumbprint: consumed.value.proofKeyThumbprint } : {}), expiresAt: consumed.value.expiresAt, } satisfies BootstrapGrant; } const matching = yield* pairingLinks.getByCredential({ credential }); if (Option.isNone(matching)) { return yield* new UnknownBootstrapCredentialError({}); } if (matching.value.revokedAt !== null) { return yield* new UnavailableBootstrapCredentialError({}); } if (matching.value.consumedAt !== null) { return yield* new UnknownBootstrapCredentialError({}); } if (DateTime.isGreaterThanOrEqualTo(now, matching.value.expiresAt)) { return yield* new ExpiredBootstrapCredentialError({}); } if ( matching.value.proofKeyThumbprint !== null && matching.value.proofKeyThumbprint !== input?.proofKeyThumbprint ) { return yield* new BootstrapCredentialProofKeyMismatchError({}); } return yield* new UnavailableBootstrapCredentialError({}); }, Effect.mapError((cause) => isBootstrapCredentialError(cause) ? cause : new BootstrapCredentialConsumeError({ cause }), ), ); return PairingGrantStore.of({ issueOneTimeToken, listActive, get streamChanges() { return Stream.fromPubSub(changesPubSub); }, revoke, consume, });}); export const layer = Layer.effect(PairingGrantStore, make).pipe( Layer.provideMerge(AuthPairingLinks.layer),);
import {
AuthAdministrativeScopes,
AuthStandardClientScopes,
type AuthEnvironmentScope,
type AuthPairingLink,
type ServerAuthBootstrapMethod,
} from "@t3tools/contracts";
import * as Context from "effect/Context";
import * as Crypto from "effect/Crypto";
import * as DateTime from "effect/DateTime";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as PubSub from "effect/PubSub";
import * as Ref from "effect/Ref";
import * as Schema from "effect/Schema";
import * as Stream from "effect/Stream";
import * as ServerConfig from "../config.ts";
import * as AuthPairingLinks from "../persistence/AuthPairingLinks.ts";
export interface BootstrapGrant {
readonly method: ServerAuthBootstrapMethod;
readonly scopes: ReadonlyArray<AuthEnvironmentScope>;
readonly subject: string;
readonly label?: string;
readonly proofKeyThumbprint?: string;
readonly expiresAt: DateTime.DateTime;
}
export class UnknownBootstrapCredentialError extends Schema.TaggedErrorClass<UnknownBootstrapCredentialError>()(
"UnknownBootstrapCredentialError",
{},
) {
override get message(): string {
return "Unknown bootstrap credential.";
}
}
export class ExpiredBootstrapCredentialError extends Schema.TaggedErrorClass<ExpiredBootstrapCredentialError>()(
"ExpiredBootstrapCredentialError",
{},
) {
override get message(): string {
return "Bootstrap credential expired.";
}
}
export class BootstrapCredentialProofKeyMismatchError extends Schema.TaggedErrorClass<BootstrapCredentialProofKeyMismatchError>()(
"BootstrapCredentialProofKeyMismatchError",
{},
) {
override get message(): string {
return "Bootstrap credential proof key mismatch.";
}
}
export class UnavailableBootstrapCredentialError extends Schema.TaggedErrorClass<UnavailableBootstrapCredentialError>()(
"UnavailableBootstrapCredentialError",
{},
) {
override get message(): string {
return "Bootstrap credential is no longer available.";
}
}
export const BootstrapCredentialInvalidError = Schema.Union([
UnknownBootstrapCredentialError,
ExpiredBootstrapCredentialError,
BootstrapCredentialProofKeyMismatchError,
UnavailableBootstrapCredentialError,
]);
export type BootstrapCredentialInvalidError = typeof BootstrapCredentialInvalidError.Type;
export const isBootstrapCredentialInvalidError = Schema.is(BootstrapCredentialInvalidError);
export class ActivePairingLinksLoadError extends Schema.TaggedErrorClass<ActivePairingLinksLoadError>()(
"ActivePairingLinksLoadError",
{
cause: Schema.Defect(),
},
) {
override get message(): string {
return "Failed to load active pairing links.";
}
}
export class PairingLinkRevokeError extends Schema.TaggedErrorClass<PairingLinkRevokeError>()(
"PairingLinkRevokeError",
{
cause: Schema.Defect(),
},
) {
override get message(): string {
return "Failed to revoke pairing link.";
}
}
export class PairingCredentialIssueError extends Schema.TaggedErrorClass<PairingCredentialIssueError>()(
"PairingCredentialIssueError",
{
cause: Schema.Defect(),
},
) {
override get message(): string {
return "Failed to issue pairing credential.";
}
}
export class BootstrapCredentialConsumeError extends Schema.TaggedErrorClass<BootstrapCredentialConsumeError>()(
"BootstrapCredentialConsumeError",
{
cause: Schema.Defect(),
},
) {
override get message(): string {
return "Failed to consume bootstrap credential.";
}
}
export const BootstrapCredentialInternalError = Schema.Union([
ActivePairingLinksLoadError,
PairingLinkRevokeError,
PairingCredentialIssueError,
BootstrapCredentialConsumeError,
]);
export type BootstrapCredentialInternalError = typeof BootstrapCredentialInternalError.Type;
export const isBootstrapCredentialInternalError = Schema.is(BootstrapCredentialInternalError);
export const BootstrapCredentialError = Schema.Union([
BootstrapCredentialInvalidError,
BootstrapCredentialInternalError,
]);
export type BootstrapCredentialError = typeof BootstrapCredentialError.Type;
export const isBootstrapCredentialError = Schema.is(BootstrapCredentialError);
export interface IssuedBootstrapCredential {
readonly id: string;
readonly credential: string;
readonly label?: string;
readonly proofKeyThumbprint?: string;
readonly expiresAt: DateTime.Utc;
}
export type BootstrapCredentialChange =
| {
readonly type: "pairingLinkUpserted";
readonly pairingLink: AuthPairingLink;
}
| {
readonly type: "pairingLinkRemoved";
readonly id: string;
};
export class PairingGrantStore extends Context.Service<
PairingGrantStore,
{
readonly issueOneTimeToken: (input?: {
readonly ttl?: Duration.Duration;
readonly scopes?: ReadonlyArray<AuthEnvironmentScope>;
readonly subject?: string;
readonly label?: string;
readonly proofKeyThumbprint?: string;
}) => Effect.Effect<IssuedBootstrapCredential, BootstrapCredentialInternalError>;
readonly listActive: () => Effect.Effect<
ReadonlyArray<AuthPairingLink>,
BootstrapCredentialInternalError
>;
readonly streamChanges: Stream.Stream<BootstrapCredentialChange>;
readonly revoke: (id: string) => Effect.Effect<boolean, BootstrapCredentialInternalError>;
readonly consume: (
credential: string,
input?: {
readonly proofKeyThumbprint?: string;
},
) => Effect.Effect<BootstrapGrant, BootstrapCredentialError>;
}
>()("t3/auth/PairingGrantStore") {}
interface StoredBootstrapGrant extends BootstrapGrant {
readonly remainingUses: number | "unbounded";
}
type ConsumeResult =
| {
readonly _tag: "error";
readonly reason: "not-found" | "expired";
readonly error: BootstrapCredentialError;
}
| {
readonly _tag: "success";
readonly grant: BootstrapGrant;
};
const DEFAULT_ONE_TIME_TOKEN_TTL_MINUTES = Duration.minutes(5);
const PAIRING_TOKEN_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
const PAIRING_TOKEN_LENGTH = 12;
const PAIRING_TOKEN_REJECTION_LIMIT =
Math.floor(256 / PAIRING_TOKEN_ALPHABET.length) * PAIRING_TOKEN_ALPHABET.length;
export const make = Effect.gen(function* () {
const crypto = yield* Crypto.Crypto;
const config = yield* ServerConfig.ServerConfig;
const pairingLinks = yield* AuthPairingLinks.AuthPairingLinkRepository;
const seededGrantsRef = yield* Ref.make(new Map<string, StoredBootstrapGrant>());
const changesPubSub = yield* PubSub.unbounded<BootstrapCredentialChange>();
const generatePairingToken = Effect.gen(function* () {
let credential = "";
while (credential.length < PAIRING_TOKEN_LENGTH) {
const bytes = yield* crypto.randomBytes(PAIRING_TOKEN_LENGTH);
for (const byte of bytes) {
if (byte >= PAIRING_TOKEN_REJECTION_LIMIT) {
continue;
}
credential += PAIRING_TOKEN_ALPHABET[byte % PAIRING_TOKEN_ALPHABET.length]!;
if (credential.length === PAIRING_TOKEN_LENGTH) {
return credential;
}
}
}
return credential;
});
const seedGrant = (credential: string, grant: StoredBootstrapGrant) =>
Ref.update(seededGrantsRef, (current) => {
const next = new Map(current);
next.set(credential, grant);
return next;
});
const emitUpsert = (pairingLink: AuthPairingLink) =>
PubSub.publish(changesPubSub, {
type: "pairingLinkUpserted",
pairingLink,
}).pipe(Effect.asVoid);
const emitRemoved = (id: string) =>
PubSub.publish(changesPubSub, {
type: "pairingLinkRemoved",
id,
}).pipe(Effect.asVoid);
if (config.desktopBootstrapToken) {
const now = yield* DateTime.now;
yield* seedGrant(config.desktopBootstrapToken, {
method: "desktop-bootstrap",
scopes: AuthAdministrativeScopes,
subject: "desktop-bootstrap",
expiresAt: DateTime.add(now, {
milliseconds: Duration.toMillis(DEFAULT_ONE_TIME_TOKEN_TTL_MINUTES),
}),
remainingUses: 1,
});
}
const listActive: PairingGrantStore["Service"]["listActive"] = Effect.fn(
"PairingGrantStore.listActive",
)(
function* () {
const now = yield* DateTime.now;
const rows = yield* pairingLinks.listActive({ now });
return rows.map((row) =>
row.label
? ({
id: row.id,
credential: row.credential,
scopes: row.scopes,
subject: row.subject,
label: row.label,
createdAt: row.createdAt,
expiresAt: row.expiresAt,
} satisfies AuthPairingLink)
: ({
id: row.id,
credential: row.credential,
scopes: row.scopes,
subject: row.subject,
createdAt: row.createdAt,
expiresAt: row.expiresAt,
} satisfies AuthPairingLink),
);
},
Effect.mapError((cause) => new ActivePairingLinksLoadError({ cause })),
);
const revoke: PairingGrantStore["Service"]["revoke"] = Effect.fn("PairingGrantStore.revoke")(
function* (id) {
const revokedAt = yield* DateTime.now;
const revoked = yield* pairingLinks.revoke({
id,
revokedAt,
});
if (revoked) {
yield* emitRemoved(id);
}
return revoked;
},
Effect.mapError((cause) => new PairingLinkRevokeError({ cause })),
);
const issueOneTimeToken: PairingGrantStore["Service"]["issueOneTimeToken"] = Effect.fn(
"PairingGrantStore.issueOneTimeToken",
)(
function* (input) {
const id = yield* crypto.randomUUIDv4;
const credential = yield* generatePairingToken;
const ttl = input?.ttl ?? DEFAULT_ONE_TIME_TOKEN_TTL_MINUTES;
const now = yield* DateTime.now;
const expiresAt = DateTime.add(now, { milliseconds: Duration.toMillis(ttl) });
const issued: IssuedBootstrapCredential = {
id,
credential,
...(input?.label ? { label: input.label } : {}),
...(input?.proofKeyThumbprint ? { proofKeyThumbprint: input.proofKeyThumbprint } : {}),
expiresAt,
};
yield* pairingLinks.create({
id,
credential,
method: "one-time-token",
scopes: input?.scopes ?? AuthStandardClientScopes,
subject: input?.subject ?? "one-time-token",
label: input?.label ?? null,
proofKeyThumbprint: input?.proofKeyThumbprint ?? null,
createdAt: now,
expiresAt: expiresAt,
});
yield* emitUpsert({
id,
credential,
scopes: input?.scopes ?? AuthStandardClientScopes,
subject: input?.subject ?? "one-time-token",
...(input?.label ? { label: input.label } : {}),
createdAt: now,
expiresAt,
});
return issued;
},
Effect.mapError((cause) => new PairingCredentialIssueError({ cause })),
);
const consume: PairingGrantStore["Service"]["consume"] = Effect.fn("PairingGrantStore.consume")(
function* (credential, input) {
const now = yield* DateTime.now;
const seededResult: ConsumeResult = yield* Ref.modify(
seededGrantsRef,
(current): readonly [ConsumeResult, Map<string, StoredBootstrapGrant>] => {
const grant = current.get(credential);
if (!grant) {
return [
{
_tag: "error",
reason: "not-found",
error: new UnknownBootstrapCredentialError({}),
},
current,
];
}
const next = new Map(current);
if (DateTime.isGreaterThanOrEqualTo(now, grant.expiresAt)) {
next.delete(credential);
return [
{
_tag: "error",
reason: "expired",
error: new ExpiredBootstrapCredentialError({}),
},
next,
];
}
if (grant.proofKeyThumbprint && grant.proofKeyThumbprint !== input?.proofKeyThumbprint) {
return [
{
_tag: "error",
reason: "not-found",
error: new BootstrapCredentialProofKeyMismatchError({}),
},
next,
];
}
const remainingUses = grant.remainingUses;
if (typeof remainingUses === "number") {
if (remainingUses <= 1) {
next.delete(credential);
} else {
next.set(credential, {
...grant,
remainingUses: remainingUses - 1,
});
}
}
return [
{
_tag: "success",
grant: {
method: grant.method,
scopes: grant.scopes,
subject: grant.subject,
...(grant.label ? { label: grant.label } : {}),
...(grant.proofKeyThumbprint
? { proofKeyThumbprint: grant.proofKeyThumbprint }
: {}),
expiresAt: grant.expiresAt,
} satisfies BootstrapGrant,
},
next,
];
},
);
if (seededResult._tag === "success") {
return seededResult.grant;
}
if (seededResult.reason !== "not-found") {
return yield* seededResult.error;
}
const consumed = yield* pairingLinks.consumeAvailable({
credential,
proofKeyThumbprint: input?.proofKeyThumbprint ?? null,
consumedAt: now,
now,
});
if (Option.isSome(consumed)) {
yield* emitRemoved(consumed.value.id);
return {
method: consumed.value.method,
scopes: consumed.value.scopes,
subject: consumed.value.subject,
...(consumed.value.label ? { label: consumed.value.label } : {}),
...(consumed.value.proofKeyThumbprint
? { proofKeyThumbprint: consumed.value.proofKeyThumbprint }
: {}),
expiresAt: consumed.value.expiresAt,
} satisfies BootstrapGrant;
}
const matching = yield* pairingLinks.getByCredential({ credential });
if (Option.isNone(matching)) {
return yield* new UnknownBootstrapCredentialError({});
}
if (matching.value.revokedAt !== null) {
return yield* new UnavailableBootstrapCredentialError({});
}
if (matching.value.consumedAt !== null) {
return yield* new UnknownBootstrapCredentialError({});
}
if (DateTime.isGreaterThanOrEqualTo(now, matching.value.expiresAt)) {
return yield* new ExpiredBootstrapCredentialError({});
}
if (
matching.value.proofKeyThumbprint !== null &&
matching.value.proofKeyThumbprint !== input?.proofKeyThumbprint
) {
return yield* new BootstrapCredentialProofKeyMismatchError({});
}
return yield* new UnavailableBootstrapCredentialError({});
},
Effect.mapError((cause) =>
isBootstrapCredentialError(cause) ? cause : new BootstrapCredentialConsumeError({ cause }),
),
);
return PairingGrantStore.of({
issueOneTimeToken,
listActive,
get streamChanges() {
return Stream.fromPubSub(changesPubSub);
},
revoke,
consume,
});
});
export const layer = Layer.effect(PairingGrantStore, make).pipe(
Layer.provideMerge(AuthPairingLinks.layer),
);