import * as Context from "effect/Context";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Schema from "effect/Schema";import * as SqlClient from "effect/unstable/sql/SqlClient";import * as SqlSchema from "effect/unstable/sql/SqlSchema"; import { AuthEnvironmentScopes } from "@t3tools/contracts"; import { type AuthPairingLinkRepositoryError, PersistenceDecodeError, type PersistenceErrorCorrelation, PersistenceSqlError,} from "./Errors.ts"; export const AuthPairingLinkRecord = Schema.Struct({ id: Schema.String, credential: Schema.String, method: Schema.Literals(["desktop-bootstrap", "one-time-token"]), scopes: Schema.fromJsonString(AuthEnvironmentScopes), subject: Schema.String, label: Schema.NullOr(Schema.String), proofKeyThumbprint: Schema.NullOr(Schema.String), createdAt: Schema.DateTimeUtcFromString, expiresAt: Schema.DateTimeUtcFromString, consumedAt: Schema.NullOr(Schema.DateTimeUtcFromString), revokedAt: Schema.NullOr(Schema.DateTimeUtcFromString),});export type AuthPairingLinkRecord = typeof AuthPairingLinkRecord.Type; export const CreateAuthPairingLinkInput = Schema.Struct({ id: Schema.String, credential: Schema.String, method: Schema.Literals(["desktop-bootstrap", "one-time-token"]), scopes: AuthEnvironmentScopes, subject: Schema.String, label: Schema.NullOr(Schema.String), proofKeyThumbprint: Schema.NullOr(Schema.String), createdAt: Schema.DateTimeUtcFromString, expiresAt: Schema.DateTimeUtcFromString,});export type CreateAuthPairingLinkInput = typeof CreateAuthPairingLinkInput.Type; export const ConsumeAuthPairingLinkInput = Schema.Struct({ credential: Schema.String, proofKeyThumbprint: Schema.NullOr(Schema.String), consumedAt: Schema.DateTimeUtcFromString, now: Schema.DateTimeUtcFromString,});export type ConsumeAuthPairingLinkInput = typeof ConsumeAuthPairingLinkInput.Type; export const ListActiveAuthPairingLinksInput = Schema.Struct({ now: Schema.DateTimeUtcFromString,});export type ListActiveAuthPairingLinksInput = typeof ListActiveAuthPairingLinksInput.Type; export const RevokeAuthPairingLinkInput = Schema.Struct({ id: Schema.String, revokedAt: Schema.DateTimeUtcFromString,});export type RevokeAuthPairingLinkInput = typeof RevokeAuthPairingLinkInput.Type; export const GetAuthPairingLinkByCredentialInput = Schema.Struct({ credential: Schema.String,});export type GetAuthPairingLinkByCredentialInput = typeof GetAuthPairingLinkByCredentialInput.Type; const AuthPairingLinkRawDbRow = Schema.Struct({ id: Schema.String, credential: Schema.Unknown, method: Schema.Unknown, scopes: Schema.Unknown, subject: Schema.Unknown, label: Schema.Unknown, proofKeyThumbprint: Schema.Unknown, createdAt: Schema.Unknown, expiresAt: Schema.Unknown, consumedAt: Schema.Unknown, revokedAt: Schema.Unknown,}); const decodeAuthPairingLinkDbRow = Schema.decodeUnknownEffect(AuthPairingLinkRecord); export class AuthPairingLinkRepository extends Context.Service< AuthPairingLinkRepository, { readonly create: ( input: CreateAuthPairingLinkInput, ) => Effect.Effect<void, AuthPairingLinkRepositoryError>; readonly consumeAvailable: ( input: ConsumeAuthPairingLinkInput, ) => Effect.Effect<Option.Option<AuthPairingLinkRecord>, AuthPairingLinkRepositoryError>; readonly listActive: ( input: ListActiveAuthPairingLinksInput, ) => Effect.Effect<ReadonlyArray<AuthPairingLinkRecord>, AuthPairingLinkRepositoryError>; readonly revoke: ( input: RevokeAuthPairingLinkInput, ) => Effect.Effect<boolean, AuthPairingLinkRepositoryError>; readonly getByCredential: ( input: GetAuthPairingLinkByCredentialInput, ) => Effect.Effect<Option.Option<AuthPairingLinkRecord>, AuthPairingLinkRepositoryError>; }>()("t3/persistence/AuthPairingLinks/AuthPairingLinkRepository") {} function toPersistenceSqlOrDecodeError( sqlOperation: string, decodeOperation: string, correlation?: PersistenceErrorCorrelation,) { return (cause: unknown): AuthPairingLinkRepositoryError => Schema.isSchemaError(cause) ? PersistenceDecodeError.fromSchemaError(decodeOperation, cause, correlation) : new PersistenceSqlError({ operation: sqlOperation, ...(correlation === undefined ? {} : { correlation }), cause, });} /** @public Service construction is part of the canonical Effect module API. */export const make = Effect.gen(function* () { const sql = yield* SqlClient.SqlClient; const createPairingLinkRow = SqlSchema.void({ Request: CreateAuthPairingLinkInput, execute: (input) => sql` INSERT INTO auth_pairing_links ( id, credential, method, scopes, subject, label, proof_key_thumbprint, created_at, expires_at, consumed_at, revoked_at ) VALUES ( ${input.id}, ${input.credential}, ${input.method}, ${JSON.stringify(input.scopes)}, ${input.subject}, ${input.label}, ${input.proofKeyThumbprint}, ${input.createdAt}, ${input.expiresAt}, NULL, NULL ) `, }); const consumeAvailablePairingLinkRow = SqlSchema.findOneOption({ Request: ConsumeAuthPairingLinkInput, Result: AuthPairingLinkRawDbRow, execute: ({ credential, proofKeyThumbprint, consumedAt, now }) => sql` UPDATE auth_pairing_links SET consumed_at = ${consumedAt} WHERE credential = ${credential} AND revoked_at IS NULL AND consumed_at IS NULL AND expires_at > ${now} AND ( proof_key_thumbprint IS NULL OR proof_key_thumbprint = ${proofKeyThumbprint} ) RETURNING id AS "id", credential AS "credential", method AS "method", scopes AS "scopes", subject AS "subject", label AS "label", proof_key_thumbprint AS "proofKeyThumbprint", created_at AS "createdAt", expires_at AS "expiresAt", consumed_at AS "consumedAt", revoked_at AS "revokedAt" `, }); const listActivePairingLinkRows = SqlSchema.findAll({ Request: ListActiveAuthPairingLinksInput, Result: AuthPairingLinkRawDbRow, execute: ({ now }) => sql` SELECT id AS "id", credential AS "credential", method AS "method", scopes AS "scopes", subject AS "subject", label AS "label", proof_key_thumbprint AS "proofKeyThumbprint", created_at AS "createdAt", expires_at AS "expiresAt", consumed_at AS "consumedAt", revoked_at AS "revokedAt" FROM auth_pairing_links WHERE revoked_at IS NULL AND consumed_at IS NULL AND expires_at > ${now} ORDER BY created_at DESC, id DESC `, }); const revokePairingLinkRow = SqlSchema.findAll({ Request: RevokeAuthPairingLinkInput, Result: Schema.Struct({ id: Schema.String }), execute: ({ id, revokedAt }) => sql` UPDATE auth_pairing_links SET revoked_at = ${revokedAt} WHERE id = ${id} AND revoked_at IS NULL AND consumed_at IS NULL RETURNING id AS "id" `, }); const getPairingLinkRowByCredential = SqlSchema.findOneOption({ Request: GetAuthPairingLinkByCredentialInput, Result: AuthPairingLinkRawDbRow, execute: ({ credential }) => sql` SELECT id AS "id", credential AS "credential", method AS "method", scopes AS "scopes", subject AS "subject", label AS "label", proof_key_thumbprint AS "proofKeyThumbprint", created_at AS "createdAt", expires_at AS "expiresAt", consumed_at AS "consumedAt", revoked_at AS "revokedAt" FROM auth_pairing_links WHERE credential = ${credential} `, }); const create: AuthPairingLinkRepository["Service"]["create"] = (input) => createPairingLinkRow(input).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "AuthPairingLinkRepository.create:query", "AuthPairingLinkRepository.create:encodeRequest", { pairingLinkId: input.id }, ), ), ); const consumeAvailable: AuthPairingLinkRepository["Service"]["consumeAvailable"] = (input) => consumeAvailablePairingLinkRow(input).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "AuthPairingLinkRepository.consumeAvailable:query", "AuthPairingLinkRepository.consumeAvailable:decodeRow", ), ), Effect.flatMap((rowOption) => Option.match(rowOption, { onNone: () => Effect.succeed(Option.none()), onSome: (row) => decodeAuthPairingLinkDbRow(row).pipe( Effect.mapError((cause) => PersistenceDecodeError.fromSchemaError( "AuthPairingLinkRepository.consumeAvailable:decodeRow", cause, { pairingLinkId: row.id }, ), ), Effect.map(Option.some), ), }), ), ); const listActive: AuthPairingLinkRepository["Service"]["listActive"] = (input) => listActivePairingLinkRows(input).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "AuthPairingLinkRepository.listActive:query", "AuthPairingLinkRepository.listActive:decodeRows", ), ), Effect.flatMap((rows) => Effect.forEach(rows, (row) => decodeAuthPairingLinkDbRow(row).pipe( Effect.mapError((cause) => PersistenceDecodeError.fromSchemaError( "AuthPairingLinkRepository.listActive:decodeRows", cause, { pairingLinkId: row.id }, ), ), ), ), ), ); const revoke: AuthPairingLinkRepository["Service"]["revoke"] = (input) => revokePairingLinkRow(input).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "AuthPairingLinkRepository.revoke:query", "AuthPairingLinkRepository.revoke:decodeRows", { pairingLinkId: input.id }, ), ), Effect.map((rows) => rows.length > 0), ); const getByCredential: AuthPairingLinkRepository["Service"]["getByCredential"] = (input) => getPairingLinkRowByCredential(input).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "AuthPairingLinkRepository.getByCredential:query", "AuthPairingLinkRepository.getByCredential:decodeRow", ), ), Effect.flatMap((rowOption) => Option.match(rowOption, { onNone: () => Effect.succeed(Option.none()), onSome: (row) => decodeAuthPairingLinkDbRow(row).pipe( Effect.mapError((cause) => PersistenceDecodeError.fromSchemaError( "AuthPairingLinkRepository.getByCredential:decodeRow", cause, { pairingLinkId: row.id }, ), ), Effect.map(Option.some), ), }), ), ); return { create, consumeAvailable, listActive, revoke, getByCredential, } satisfies AuthPairingLinkRepository["Service"];}); export const layer = Layer.effect(AuthPairingLinkRepository, make);
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as Schema from "effect/Schema";
import * as SqlClient from "effect/unstable/sql/SqlClient";
import * as SqlSchema from "effect/unstable/sql/SqlSchema";
import { AuthEnvironmentScopes } from "@t3tools/contracts";
import {
type AuthPairingLinkRepositoryError,
PersistenceDecodeError,
type PersistenceErrorCorrelation,
PersistenceSqlError,
} from "./Errors.ts";
export const AuthPairingLinkRecord = Schema.Struct({
id: Schema.String,
credential: Schema.String,
method: Schema.Literals(["desktop-bootstrap", "one-time-token"]),
scopes: Schema.fromJsonString(AuthEnvironmentScopes),
subject: Schema.String,
label: Schema.NullOr(Schema.String),
proofKeyThumbprint: Schema.NullOr(Schema.String),
createdAt: Schema.DateTimeUtcFromString,
expiresAt: Schema.DateTimeUtcFromString,
consumedAt: Schema.NullOr(Schema.DateTimeUtcFromString),
revokedAt: Schema.NullOr(Schema.DateTimeUtcFromString),
});
export type AuthPairingLinkRecord = typeof AuthPairingLinkRecord.Type;
export const CreateAuthPairingLinkInput = Schema.Struct({
id: Schema.String,
credential: Schema.String,
method: Schema.Literals(["desktop-bootstrap", "one-time-token"]),
scopes: AuthEnvironmentScopes,
subject: Schema.String,
label: Schema.NullOr(Schema.String),
proofKeyThumbprint: Schema.NullOr(Schema.String),
createdAt: Schema.DateTimeUtcFromString,
expiresAt: Schema.DateTimeUtcFromString,
});
export type CreateAuthPairingLinkInput = typeof CreateAuthPairingLinkInput.Type;
export const ConsumeAuthPairingLinkInput = Schema.Struct({
credential: Schema.String,
proofKeyThumbprint: Schema.NullOr(Schema.String),
consumedAt: Schema.DateTimeUtcFromString,
now: Schema.DateTimeUtcFromString,
});
export type ConsumeAuthPairingLinkInput = typeof ConsumeAuthPairingLinkInput.Type;
export const ListActiveAuthPairingLinksInput = Schema.Struct({
now: Schema.DateTimeUtcFromString,
});
export type ListActiveAuthPairingLinksInput = typeof ListActiveAuthPairingLinksInput.Type;
export const RevokeAuthPairingLinkInput = Schema.Struct({
id: Schema.String,
revokedAt: Schema.DateTimeUtcFromString,
});
export type RevokeAuthPairingLinkInput = typeof RevokeAuthPairingLinkInput.Type;
export const GetAuthPairingLinkByCredentialInput = Schema.Struct({
credential: Schema.String,
});
export type GetAuthPairingLinkByCredentialInput = typeof GetAuthPairingLinkByCredentialInput.Type;
const AuthPairingLinkRawDbRow = Schema.Struct({
id: Schema.String,
credential: Schema.Unknown,
method: Schema.Unknown,
scopes: Schema.Unknown,
subject: Schema.Unknown,
label: Schema.Unknown,
proofKeyThumbprint: Schema.Unknown,
createdAt: Schema.Unknown,
expiresAt: Schema.Unknown,
consumedAt: Schema.Unknown,
revokedAt: Schema.Unknown,
});
const decodeAuthPairingLinkDbRow = Schema.decodeUnknownEffect(AuthPairingLinkRecord);
export class AuthPairingLinkRepository extends Context.Service<
AuthPairingLinkRepository,
{
readonly create: (
input: CreateAuthPairingLinkInput,
) => Effect.Effect<void, AuthPairingLinkRepositoryError>;
readonly consumeAvailable: (
input: ConsumeAuthPairingLinkInput,
) => Effect.Effect<Option.Option<AuthPairingLinkRecord>, AuthPairingLinkRepositoryError>;
readonly listActive: (
input: ListActiveAuthPairingLinksInput,
) => Effect.Effect<ReadonlyArray<AuthPairingLinkRecord>, AuthPairingLinkRepositoryError>;
readonly revoke: (
input: RevokeAuthPairingLinkInput,
) => Effect.Effect<boolean, AuthPairingLinkRepositoryError>;
readonly getByCredential: (
input: GetAuthPairingLinkByCredentialInput,
) => Effect.Effect<Option.Option<AuthPairingLinkRecord>, AuthPairingLinkRepositoryError>;
}
>()("t3/persistence/AuthPairingLinks/AuthPairingLinkRepository") {}
function toPersistenceSqlOrDecodeError(
sqlOperation: string,
decodeOperation: string,
correlation?: PersistenceErrorCorrelation,
) {
return (cause: unknown): AuthPairingLinkRepositoryError =>
Schema.isSchemaError(cause)
? PersistenceDecodeError.fromSchemaError(decodeOperation, cause, correlation)
: new PersistenceSqlError({
operation: sqlOperation,
...(correlation === undefined ? {} : { correlation }),
cause,
});
}
/** @public Service construction is part of the canonical Effect module API. */
export const make = Effect.gen(function* () {
const sql = yield* SqlClient.SqlClient;
const createPairingLinkRow = SqlSchema.void({
Request: CreateAuthPairingLinkInput,
execute: (input) =>
sql`
INSERT INTO auth_pairing_links (
id,
credential,
method,
scopes,
subject,
label,
proof_key_thumbprint,
created_at,
expires_at,
consumed_at,
revoked_at
)
VALUES (
${input.id},
${input.credential},
${input.method},
${JSON.stringify(input.scopes)},
${input.subject},
${input.label},
${input.proofKeyThumbprint},
${input.createdAt},
${input.expiresAt},
NULL,
NULL
)
`,
});
const consumeAvailablePairingLinkRow = SqlSchema.findOneOption({
Request: ConsumeAuthPairingLinkInput,
Result: AuthPairingLinkRawDbRow,
execute: ({ credential, proofKeyThumbprint, consumedAt, now }) =>
sql`
UPDATE auth_pairing_links
SET consumed_at = ${consumedAt}
WHERE credential = ${credential}
AND revoked_at IS NULL
AND consumed_at IS NULL
AND expires_at > ${now}
AND (
proof_key_thumbprint IS NULL
OR proof_key_thumbprint = ${proofKeyThumbprint}
)
RETURNING
id AS "id",
credential AS "credential",
method AS "method",
scopes AS "scopes",
subject AS "subject",
label AS "label",
proof_key_thumbprint AS "proofKeyThumbprint",
created_at AS "createdAt",
expires_at AS "expiresAt",
consumed_at AS "consumedAt",
revoked_at AS "revokedAt"
`,
});
const listActivePairingLinkRows = SqlSchema.findAll({
Request: ListActiveAuthPairingLinksInput,
Result: AuthPairingLinkRawDbRow,
execute: ({ now }) =>
sql`
SELECT
id AS "id",
credential AS "credential",
method AS "method",
scopes AS "scopes",
subject AS "subject",
label AS "label",
proof_key_thumbprint AS "proofKeyThumbprint",
created_at AS "createdAt",
expires_at AS "expiresAt",
consumed_at AS "consumedAt",
revoked_at AS "revokedAt"
FROM auth_pairing_links
WHERE revoked_at IS NULL
AND consumed_at IS NULL
AND expires_at > ${now}
ORDER BY created_at DESC, id DESC
`,
});
const revokePairingLinkRow = SqlSchema.findAll({
Request: RevokeAuthPairingLinkInput,
Result: Schema.Struct({ id: Schema.String }),
execute: ({ id, revokedAt }) =>
sql`
UPDATE auth_pairing_links
SET revoked_at = ${revokedAt}
WHERE id = ${id}
AND revoked_at IS NULL
AND consumed_at IS NULL
RETURNING id AS "id"
`,
});
const getPairingLinkRowByCredential = SqlSchema.findOneOption({
Request: GetAuthPairingLinkByCredentialInput,
Result: AuthPairingLinkRawDbRow,
execute: ({ credential }) =>
sql`
SELECT
id AS "id",
credential AS "credential",
method AS "method",
scopes AS "scopes",
subject AS "subject",
label AS "label",
proof_key_thumbprint AS "proofKeyThumbprint",
created_at AS "createdAt",
expires_at AS "expiresAt",
consumed_at AS "consumedAt",
revoked_at AS "revokedAt"
FROM auth_pairing_links
WHERE credential = ${credential}
`,
});
const create: AuthPairingLinkRepository["Service"]["create"] = (input) =>
createPairingLinkRow(input).pipe(
Effect.mapError(
toPersistenceSqlOrDecodeError(
"AuthPairingLinkRepository.create:query",
"AuthPairingLinkRepository.create:encodeRequest",
{ pairingLinkId: input.id },
),
),
);
const consumeAvailable: AuthPairingLinkRepository["Service"]["consumeAvailable"] = (input) =>
consumeAvailablePairingLinkRow(input).pipe(
Effect.mapError(
toPersistenceSqlOrDecodeError(
"AuthPairingLinkRepository.consumeAvailable:query",
"AuthPairingLinkRepository.consumeAvailable:decodeRow",
),
),
Effect.flatMap((rowOption) =>
Option.match(rowOption, {
onNone: () => Effect.succeed(Option.none()),
onSome: (row) =>
decodeAuthPairingLinkDbRow(row).pipe(
Effect.mapError((cause) =>
PersistenceDecodeError.fromSchemaError(
"AuthPairingLinkRepository.consumeAvailable:decodeRow",
cause,
{ pairingLinkId: row.id },
),
),
Effect.map(Option.some),
),
}),
),
);
const listActive: AuthPairingLinkRepository["Service"]["listActive"] = (input) =>
listActivePairingLinkRows(input).pipe(
Effect.mapError(
toPersistenceSqlOrDecodeError(
"AuthPairingLinkRepository.listActive:query",
"AuthPairingLinkRepository.listActive:decodeRows",
),
),
Effect.flatMap((rows) =>
Effect.forEach(rows, (row) =>
decodeAuthPairingLinkDbRow(row).pipe(
Effect.mapError((cause) =>
PersistenceDecodeError.fromSchemaError(
"AuthPairingLinkRepository.listActive:decodeRows",
cause,
{ pairingLinkId: row.id },
),
),
),
),
),
);
const revoke: AuthPairingLinkRepository["Service"]["revoke"] = (input) =>
revokePairingLinkRow(input).pipe(
Effect.mapError(
toPersistenceSqlOrDecodeError(
"AuthPairingLinkRepository.revoke:query",
"AuthPairingLinkRepository.revoke:decodeRows",
{ pairingLinkId: input.id },
),
),
Effect.map((rows) => rows.length > 0),
);
const getByCredential: AuthPairingLinkRepository["Service"]["getByCredential"] = (input) =>
getPairingLinkRowByCredential(input).pipe(
Effect.mapError(
toPersistenceSqlOrDecodeError(
"AuthPairingLinkRepository.getByCredential:query",
"AuthPairingLinkRepository.getByCredential:decodeRow",
),
),
Effect.flatMap((rowOption) =>
Option.match(rowOption, {
onNone: () => Effect.succeed(Option.none()),
onSome: (row) =>
decodeAuthPairingLinkDbRow(row).pipe(
Effect.mapError((cause) =>
PersistenceDecodeError.fromSchemaError(
"AuthPairingLinkRepository.getByCredential:decodeRow",
cause,
{ pairingLinkId: row.id },
),
),
Effect.map(Option.some),
),
}),
),
);
return {
create,
consumeAvailable,
listActive,
revoke,
getByCredential,
} satisfies AuthPairingLinkRepository["Service"];
});
export const layer = Layer.effect(AuthPairingLinkRepository, make);