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