Skip to content

Commit 6068555

Browse files
Derive session cookie names from server mode
- Use port-scoped cookies for desktop mode - Log rejected session credentials with reasons - Update auth policy and server tests
1 parent 1cba2f6 commit 6068555

6 files changed

Lines changed: 54 additions & 6 deletions

File tree

apps/server/src/auth/Layers/ServerAuth.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,24 @@ type BootstrapExchangeResult = {
3131
const AUTHORIZATION_PREFIX = "Bearer ";
3232
const WEBSOCKET_TOKEN_QUERY_PARAM = "wsToken";
3333

34+
function authFailureReason(cause: unknown): string {
35+
if (cause instanceof Error && cause.message.trim().length > 0) {
36+
return cause.message;
37+
}
38+
39+
if (
40+
typeof cause === "object" &&
41+
cause !== null &&
42+
"message" in cause &&
43+
typeof cause.message === "string" &&
44+
cause.message.trim().length > 0
45+
) {
46+
return cause.message;
47+
}
48+
49+
return "unknown";
50+
}
51+
3452
export function toBootstrapExchangeAuthError(cause: BootstrapCredentialError): AuthError {
3553
if (cause.status === 500) {
3654
return new AuthError({
@@ -65,6 +83,13 @@ export const makeServerAuth = Effect.gen(function* () {
6583

6684
const authenticateToken = (token: string): Effect.Effect<AuthenticatedSession, AuthError> =>
6785
sessions.verify(token).pipe(
86+
Effect.tapError((cause) =>
87+
Effect.logWarning("Rejected authenticated session credential.").pipe(
88+
Effect.annotateLogs({
89+
reason: authFailureReason(cause),
90+
}),
91+
),
92+
),
6893
Effect.map((session) => ({
6994
sessionId: session.sessionId,
7095
subject: session.subject,

apps/server/src/auth/Layers/ServerAuthPolicy.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@ it.layer(NodeServices.layer)("ServerAuthPolicyLive", (it) => {
3333

3434
expect(descriptor.policy).toBe("desktop-managed-local");
3535
expect(descriptor.bootstrapMethods).toEqual(["desktop-bootstrap"]);
36+
expect(descriptor.sessionCookieName).toBe("t3_session_3773");
3637
}).pipe(
3738
Effect.provide(
3839
makeServerAuthPolicyLayer({
3940
mode: "desktop",
41+
port: 3773,
4042
}),
4143
),
4244
),
@@ -66,6 +68,7 @@ it.layer(NodeServices.layer)("ServerAuthPolicyLive", (it) => {
6668

6769
expect(descriptor.policy).toBe("loopback-browser");
6870
expect(descriptor.bootstrapMethods).toEqual(["one-time-token"]);
71+
expect(descriptor.sessionCookieName).toBe("t3_session");
6972
}).pipe(
7073
Effect.provide(
7174
makeServerAuthPolicyLayer({

apps/server/src/auth/Layers/ServerAuthPolicy.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Effect, Layer } from "effect";
33

44
import { ServerConfig } from "../../config.ts";
55
import { ServerAuthPolicy, type ServerAuthPolicyShape } from "../Services/ServerAuthPolicy.ts";
6-
import { SESSION_COOKIE_NAME } from "../utils.ts";
6+
import { resolveSessionCookieName } from "../utils.ts";
77
import { isLoopbackHost, isWildcardHost } from "../../startupAccess.ts";
88

99
export const makeServerAuthPolicy = Effect.gen(function* () {
@@ -30,7 +30,10 @@ export const makeServerAuthPolicy = Effect.gen(function* () {
3030
policy,
3131
bootstrapMethods,
3232
sessionMethods: ["browser-session-cookie", "bearer-session-token"],
33-
sessionCookieName: SESSION_COOKIE_NAME,
33+
sessionCookieName: resolveSessionCookieName({
34+
mode: config.mode,
35+
port: config.port,
36+
}),
3437
};
3538

3639
return {

apps/server/src/auth/Layers/SessionCredentialService.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { AuthSessionId, type AuthClientMetadata, type AuthClientSession } from "
22
import { Clock, DateTime, Duration, Effect, Layer, PubSub, Ref, Schema, Stream } from "effect";
33
import { Option } from "effect";
44

5+
import { ServerConfig } from "../../config.ts";
56
import { AuthSessionRepositoryLive } from "../../persistence/Layers/AuthSessions.ts";
67
import { AuthSessionRepository } from "../../persistence/Services/AuthSessions.ts";
78
import { ServerSecretStore } from "../Services/ServerSecretStore.ts";
8-
import { SESSION_COOKIE_NAME } from "../utils.ts";
99
import {
1010
SessionCredentialError,
1111
SessionCredentialService,
@@ -17,6 +17,7 @@ import {
1717
import {
1818
base64UrlDecodeUtf8,
1919
base64UrlEncode,
20+
resolveSessionCookieName,
2021
signPayload,
2122
timingSafeEqualBase64Url,
2223
} from "../utils.ts";
@@ -81,11 +82,16 @@ function toAuthClientSession(input: Omit<AuthClientSession, "current">): AuthCli
8182
}
8283

8384
export const makeSessionCredentialService = Effect.gen(function* () {
85+
const serverConfig = yield* ServerConfig;
8486
const secretStore = yield* ServerSecretStore;
8587
const authSessions = yield* AuthSessionRepository;
8688
const signingSecret = yield* secretStore.getOrCreateRandom(SIGNING_SECRET_NAME, 32);
8789
const connectedSessionsRef = yield* Ref.make(new Map<string, number>());
8890
const changesPubSub = yield* PubSub.unbounded<SessionCredentialChange>();
91+
const cookieName = resolveSessionCookieName({
92+
mode: serverConfig.mode,
93+
port: serverConfig.port,
94+
});
8995

9096
const toSessionCredentialError = (message: string) => (cause: unknown) =>
9197
new SessionCredentialError({
@@ -472,7 +478,7 @@ export const makeSessionCredentialService = Effect.gen(function* () {
472478
}).pipe(Effect.mapError(toSessionCredentialError("Failed to revoke other sessions.")));
473479

474480
return {
475-
cookieName: SESSION_COOKIE_NAME,
481+
cookieName,
476482
issue,
477483
verify,
478484
issueWebSocketToken,

apps/server/src/auth/utils.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,18 @@ import type { AuthClientMetadata, AuthClientMetadataDeviceType } from "@t3tools/
22
import type * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
33
import * as Crypto from "node:crypto";
44

5-
export const SESSION_COOKIE_NAME = "t3_session";
5+
const SESSION_COOKIE_NAME = "t3_session";
6+
7+
export function resolveSessionCookieName(input: {
8+
readonly mode: "web" | "desktop";
9+
readonly port: number;
10+
}): string {
11+
if (input.mode !== "desktop") {
12+
return SESSION_COOKIE_NAME;
13+
}
14+
15+
return `${SESSION_COOKIE_NAME}_${input.port}`;
16+
}
617

718
export function base64UrlEncode(input: string | Uint8Array): string {
819
const buffer = typeof input === "string" ? Buffer.from(input, "utf8") : Buffer.from(input);

apps/server/src/server.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -789,7 +789,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
789789
"browser-session-cookie",
790790
"bearer-session-token",
791791
]);
792-
assert.equal(body.auth.sessionCookieName, "t3_session");
792+
assert.isTrue(body.auth.sessionCookieName.startsWith("t3_session_"));
793793
}).pipe(Effect.provide(NodeHttpServer.layerTest)),
794794
);
795795

0 commit comments

Comments
 (0)