Showing the changes to apps/server/src (6 of 11 files). Show all
Unified · Split
| @@ -20,7 +20,10 @@ import { · 19 unmodified lines | ||
| 20 | 20 | AuthError, |
| 21 | 21 | type ServerAuthShape, |
| 22 | 22 | } from "../Services/ServerAuth.ts"; |
| 23 | −import { SessionCredentialService } from "../Services/SessionCredentialService.ts"; | |
| 23 | +import { | |
| 24 | + SessionCredentialError, | |
| 25 | + SessionCredentialService, | |
| 26 | +} from "../Services/SessionCredentialService.ts"; | |
| 24 | 27 | import { AuthControlPlaneLive, AuthCoreLive } from "./AuthControlPlane.ts"; |
| 25 | 28 | |
| 26 | 29 | type BootstrapExchangeResult = { |
| @@ -65,6 +68,13 @@ export const makeServerAuth = Effect.gen(function* () { · 38 unmodified lines | ||
| 65 | 68 | |
| 66 | 69 | const authenticateToken = (token: string): Effect.Effect<AuthenticatedSession, AuthError> => |
| 67 | 70 | sessions.verify(token).pipe( |
| 71 | + Effect.tapError((cause: SessionCredentialError) => | |
| 72 | + Effect.logWarning("Rejected authenticated session credential.").pipe( | |
| 73 | + Effect.annotateLogs({ | |
| 74 | + reason: cause.message, | |
| 75 | + }), | |
| 76 | + ), | |
| 77 | + ), | |
| 68 | 78 | Effect.map((session) => ({ |
| 69 | 79 | sessionId: session.sessionId, |
| 70 | 80 | subject: session.subject, |
| @@ -33,10 +33,12 @@ it.layer(NodeServices.layer)("ServerAuthPolicyLive", (it) => { · 32 unmodified lines | ||
| 33 | 33 | |
| 34 | 34 | expect(descriptor.policy).toBe("desktop-managed-local"); |
| 35 | 35 | expect(descriptor.bootstrapMethods).toEqual(["desktop-bootstrap"]); |
| 36 | + expect(descriptor.sessionCookieName).toBe("t3_session_3773"); | |
| 36 | 37 | }).pipe( |
| 37 | 38 | Effect.provide( |
| 38 | 39 | makeServerAuthPolicyLayer({ |
| 39 | 40 | mode: "desktop", |
| 41 | + port: 3773, | |
| 40 | 42 | }), |
| 41 | 43 | ), |
| 42 | 44 | ), |
| @@ -66,6 +68,7 @@ it.layer(NodeServices.layer)("ServerAuthPolicyLive", (it) => { · 23 unmodified lines | ||
| 66 | 68 | |
| 67 | 69 | expect(descriptor.policy).toBe("loopback-browser"); |
| 68 | 70 | expect(descriptor.bootstrapMethods).toEqual(["one-time-token"]); |
| 71 | + expect(descriptor.sessionCookieName).toBe("t3_session"); | |
| 69 | 72 | }).pipe( |
| 70 | 73 | Effect.provide( |
| 71 | 74 | makeServerAuthPolicyLayer({ |
| @@ -3,7 +3,7 @@ import { Effect, Layer } from "effect"; · 2 unmodified lines | ||
| 3 | 3 | |
| 4 | 4 | import { ServerConfig } from "../../config.ts"; |
| 5 | 5 | import { ServerAuthPolicy, type ServerAuthPolicyShape } from "../Services/ServerAuthPolicy.ts"; |
| 6 | −import { SESSION_COOKIE_NAME } from "../utils.ts"; | |
| 6 | +import { resolveSessionCookieName } from "../utils.ts"; | |
| 7 | 7 | import { isLoopbackHost, isWildcardHost } from "../../startupAccess.ts"; |
| 8 | 8 | |
| 9 | 9 | export const makeServerAuthPolicy = Effect.gen(function* () { |
| @@ -30,7 +30,10 @@ export const makeServerAuthPolicy = Effect.gen(function* () { · 20 unmodified lines | ||
| 30 | 30 | policy, |
| 31 | 31 | bootstrapMethods, |
| 32 | 32 | sessionMethods: ["browser-session-cookie", "bearer-session-token"], |
| 33 | − sessionCookieName: SESSION_COOKIE_NAME, | |
| 33 | + sessionCookieName: resolveSessionCookieName({ | |
| 34 | + mode: config.mode, | |
| 35 | + port: config.port, | |
| 36 | + }), | |
| 34 | 37 | }; |
| 35 | 38 | |
| 36 | 39 | return { |
| @@ -2,10 +2,10 @@ import { AuthSessionId, type AuthClientMetadata, type AuthClientSession } from " · 1 unmodified line | ||
| 2 | 2 | import { Clock, DateTime, Duration, Effect, Layer, PubSub, Ref, Schema, Stream } from "effect"; |
| 3 | 3 | import { Option } from "effect"; |
| 4 | 4 | |
| 5 | +import { ServerConfig } from "../../config.ts"; | |
| 5 | 6 | import { AuthSessionRepositoryLive } from "../../persistence/Layers/AuthSessions.ts"; |
| 6 | 7 | import { AuthSessionRepository } from "../../persistence/Services/AuthSessions.ts"; |
| 7 | 8 | import { ServerSecretStore } from "../Services/ServerSecretStore.ts"; |
| 8 | −import { SESSION_COOKIE_NAME } from "../utils.ts"; | |
| 9 | 9 | import { |
| 10 | 10 | SessionCredentialError, |
| 11 | 11 | SessionCredentialService, |
| @@ -17,6 +17,7 @@ import { · 5 unmodified lines | ||
| 17 | 17 | import { |
| 18 | 18 | base64UrlDecodeUtf8, |
| 19 | 19 | base64UrlEncode, |
| 20 | + resolveSessionCookieName, | |
| 20 | 21 | signPayload, |
| 21 | 22 | timingSafeEqualBase64Url, |
| 22 | 23 | } from "../utils.ts"; |
| @@ -81,11 +82,16 @@ function toAuthClientSession(input: Omit<AuthClientSession, "current">): AuthCli · 58 unmodified lines | ||
| 81 | 82 | } |
| 82 | 83 | |
| 83 | 84 | export const makeSessionCredentialService = Effect.gen(function* () { |
| 85 | + const serverConfig = yield* ServerConfig; | |
| 84 | 86 | const secretStore = yield* ServerSecretStore; |
| 85 | 87 | const authSessions = yield* AuthSessionRepository; |
| 86 | 88 | const signingSecret = yield* secretStore.getOrCreateRandom(SIGNING_SECRET_NAME, 32); |
| 87 | 89 | const connectedSessionsRef = yield* Ref.make(new Map<string, number>()); |
| 88 | 90 | const changesPubSub = yield* PubSub.unbounded<SessionCredentialChange>(); |
| 91 | + const cookieName = resolveSessionCookieName({ | |
| 92 | + mode: serverConfig.mode, | |
| 93 | + port: serverConfig.port, | |
| 94 | + }); | |
| 89 | 95 | |
| 90 | 96 | const toSessionCredentialError = (message: string) => (cause: unknown) => |
| 91 | 97 | new SessionCredentialError({ |
| @@ -472,7 +478,7 @@ export const makeSessionCredentialService = Effect.gen(function* () { · 380 unmodified lines | ||
| 472 | 478 | }).pipe(Effect.mapError(toSessionCredentialError("Failed to revoke other sessions."))); |
| 473 | 479 | |
| 474 | 480 | return { |
| 475 | − cookieName: SESSION_COOKIE_NAME, | |
| 481 | + cookieName, | |
| 476 | 482 | issue, |
| 477 | 483 | verify, |
| 478 | 484 | issueWebSocketToken, |
| @@ -2,7 +2,18 @@ import type { AuthClientMetadata, AuthClientMetadataDeviceType } from "@t3tools/ · 1 unmodified line | ||
| 2 | 2 | import type * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; |
| 3 | 3 | import * as Crypto from "node:crypto"; |
| 4 | 4 | |
| 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 | +} | |
| 6 | 17 | |
| 7 | 18 | export function base64UrlEncode(input: string | Uint8Array): string { |
| 8 | 19 | const buffer = typeof input === "string" ? Buffer.from(input, "utf8") : Buffer.from(input); |
| @@ -789,7 +789,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { · 788 unmodified lines | ||
| 789 | 789 | "browser-session-cookie", |
| 790 | 790 | "bearer-session-token", |
| 791 | 791 | ]); |
| 792 | − assert.equal(body.auth.sessionCookieName, "t3_session"); | |
| 792 | + assert.isTrue(body.auth.sessionCookieName.startsWith("t3_session_")); | |
| 793 | 793 | }).pipe(Effect.provide(NodeHttpServer.layerTest)), |
| 794 | 794 | ); |
| 795 | 795 | |