1import type { AuthClientMetadata, AuthClientSession, AuthPairingLink } from "@t3tools/contracts";
2import * as DateTime from "effect/DateTime";
3
4import type { IssuedBearerSession, IssuedPairingLink } from "./auth/EnvironmentAuth.ts";
5
6const newline = "\n";
7
8function serializeOptionalFields(values: ReadonlyArray<string | null | undefined>) {
9 return values.filter((value): value is string => typeof value === "string" && value.length > 0);
10}
11
12function formatClientMetadata(metadata: AuthClientMetadata): string {
13 const details = serializeOptionalFields([
14 metadata.label,
15 metadata.deviceType !== "unknown" ? metadata.deviceType : undefined,
16 metadata.os,
17 metadata.browser,
18 metadata.ipAddress,
19 ]);
20 return details.length > 0 ? details.join(" | ") : "unlabeled client";
21}
22
23function toIsoString(value: DateTime.DateTime | DateTime.Utc): string {
24 return DateTime.formatIso(DateTime.toUtc(value));
25}
26
27export function formatIssuedPairingCredential(
28 credential: IssuedPairingLink,
29 options?: {
30 readonly json?: boolean;
31 readonly baseUrl?: string;
32 },
33): string {
34 const pairUrl =
35 options?.baseUrl != null && options.baseUrl.length > 0
36 ? (() => {
37 const url = new URL("/pair", options.baseUrl);
38 url.searchParams.delete("token");
39 url.hash = new URLSearchParams([["token", credential.credential]]).toString();
40 return url.toString();
41 })()
42 : undefined;
43
44 if (options?.json) {
45 return `${JSON.stringify(
46 {
47 id: credential.id,
48 credential: credential.credential,
49 ...(credential.label ? { label: credential.label } : {}),
50 scopes: credential.scopes,
51 expiresAt: toIsoString(credential.expiresAt),
52 ...(pairUrl ? { pairUrl } : {}),
53 },
54 null,
55 2,
56 )}${newline}`;
57 }
58
59 return (
60 [
61 `Issued client pairing token ${credential.id}.`,
62 `Token: ${credential.credential}`,
63 ...(pairUrl ? [`Pair URL: ${pairUrl}`] : []),
64 `Expires at: ${toIsoString(credential.expiresAt)}`,
65 ].join(newline) + newline
66 );
67}
68
69export function formatPairingCredentialList(
70 credentials: ReadonlyArray<AuthPairingLink>,
71 options?: {
72 readonly json?: boolean;
73 },
74): string {
75 if (options?.json) {
76 return `${JSON.stringify(
77 credentials.map((credential) => ({
78 id: credential.id,
79 ...(credential.label ? { label: credential.label } : {}),
80 scopes: credential.permissions ?? credential.scopes,
81 createdAt: toIsoString(credential.createdAt),
82 expiresAt: toIsoString(credential.expiresAt),
83 })),
84 null,
85 2,
86 )}${newline}`;
87 }
88
89 if (credentials.length === 0) {
90 return `No active pairing credentials.${newline}`;
91 }
92
93 return (
94 credentials
95 .map((credential) =>
96 [
97 `${credential.id}${credential.label ? ` (${credential.label})` : ""}`,
98 ` scopes: ${(credential.permissions ?? credential.scopes).join(" ")}`,
99 ` created: ${toIsoString(credential.createdAt)}`,
100 ` expires: ${toIsoString(credential.expiresAt)}`,
101 ].join(newline),
102 )
103 .join(`${newline}${newline}`) + newline
104 );
105}
106
107export function formatIssuedSession(
108 session: IssuedBearerSession,
109 options?: {
110 readonly json?: boolean;
111 readonly tokenOnly?: boolean;
112 },
113): string {
114 if (options?.tokenOnly) {
115 return `${session.token}${newline}`;
116 }
117
118 if (options?.json) {
119 return `${JSON.stringify(
120 {
121 sessionId: session.sessionId,
122 token: session.token,
123 method: session.method,
124 scopes: session.scopes,
125 subject: session.subject,
126 client: session.client,
127 expiresAt: toIsoString(session.expiresAt),
128 },
129 null,
130 2,
131 )}${newline}`;
132 }
133
134 return (
135 [
136 `Issued bearer access token ${session.sessionId}.`,
137 `Scopes: ${session.scopes.join(" ")}`,
138 `Token: ${session.token}`,
139 `Subject: ${session.subject}`,
140 `Client: ${formatClientMetadata(session.client)}`,
141 `Expires at: ${toIsoString(session.expiresAt)}`,
142 ].join(newline) + newline
143 );
144}
145
146export function formatSessionList(
147 sessions: ReadonlyArray<AuthClientSession>,
148 options?: {
149 readonly json?: boolean;
150 },
151): string {
152 if (options?.json) {
153 return `${JSON.stringify(
154 sessions.map((session) => ({
155 sessionId: session.sessionId,
156 method: session.method,
157 scopes: session.scopes,
158 subject: session.subject,
159 client: session.client,
160 connected: session.connected,
161 issuedAt: toIsoString(session.issuedAt),
162 expiresAt: toIsoString(session.expiresAt),
163 lastConnectedAt: session.lastConnectedAt ? toIsoString(session.lastConnectedAt) : null,
164 })),
165 null,
166 2,
167 )}${newline}`;
168 }
169
170 if (sessions.length === 0) {
171 return `No active sessions.${newline}`;
172 }
173
174 return (
175 sessions
176 .map((session) =>
177 [
178 `${session.sessionId}${session.connected ? " connected" : ""}`,
179 ` scopes: ${session.scopes.join(" ")}`,
180 ` method: ${session.method}`,
181 ` subject: ${session.subject}`,
182 ` client: ${formatClientMetadata(session.client)}`,
183 ` issued: ${toIsoString(session.issuedAt)}`,
184 ` last connected: ${
185 session.lastConnectedAt ? toIsoString(session.lastConnectedAt) : "never"
186 }`,
187 ` expires: ${toIsoString(session.expiresAt)}`,
188 ].join(newline),
189 )
190 .join(`${newline}${newline}`) + newline
191 );
192}