From 9f1f2910e421fe7b79fb16a3d4faeb82b6c71b82 Mon Sep 17 00:00:00 2001 From: Milo Schwartz Date: Sun, 26 Jan 2025 14:42:02 -0500 Subject: [PATCH] refactor auth to work cross domain and with http resources closes #100 --- config/config.example.yml | 4 +- install/fs/config.yml | 4 +- package.json | 1 + server/auth/sessions/app.ts | 42 +++-- server/auth/sessions/resource.ts | 41 ++-- server/db/schema.ts | 4 + server/index.ts | 3 +- server/lib/config.ts | 18 +- server/routers/auth/logout.ts | 11 +- server/routers/badger/exchangeSession.ts | 175 ++++++++++++++++++ server/routers/badger/index.ts | 1 + server/routers/badger/verifySession.ts | 175 ++++++++++++------ server/routers/internal.ts | 12 +- .../routers/resource/authWithAccessToken.ts | 16 +- server/routers/resource/authWithPassword.ts | 11 +- server/routers/resource/authWithPincode.ts | 18 +- server/routers/resource/authWithWhitelist.ts | 12 +- server/routers/resource/getExchangeToken.ts | 109 +++++++++++ server/routers/resource/index.ts | 1 + server/routers/resource/updateResource.ts | 4 + server/routers/traefik/getTraefikConfig.ts | 12 +- server/setup/scripts/1.0.0-beta9.ts | 97 ++++++++++ .../resource/[resourceId]/AccessToken.tsx | 19 +- .../[resourceId]/ResourceAuthPortal.tsx | 37 ++-- src/app/auth/resource/[resourceId]/page.tsx | 56 +++--- src/lib/pullEnv.ts | 4 +- src/lib/types/env.ts | 2 +- 27 files changed, 688 insertions(+), 201 deletions(-) create mode 100644 server/routers/badger/exchangeSession.ts create mode 100644 server/routers/resource/getExchangeToken.ts diff --git a/config/config.example.yml b/config/config.example.yml index aca7bfe..df4170a 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -10,9 +10,9 @@ server: next_port: 3002 internal_hostname: "pangolin" secure_cookies: true - session_cookie_name: "p_session" - resource_session_cookie_name: "p_resource_session" + session_cookie_name: "p_session_token" resource_access_token_param: "p_token" + resource_session_request_param: "p_session_request" traefik: cert_resolver: "letsencrypt" diff --git a/install/fs/config.yml b/install/fs/config.yml index 6cea3f1..e732556 100644 --- a/install/fs/config.yml +++ b/install/fs/config.yml @@ -10,9 +10,9 @@ server: next_port: 3002 internal_hostname: "pangolin" secure_cookies: true - session_cookie_name: "p_session" - resource_session_cookie_name: "p_resource_session" + session_cookie_name: "p_session_token" resource_access_token_param: "p_token" + resource_session_request_param: "p_session_request" cors: origins: ["https://{{.DashboardDomain}}"] methods: ["GET", "POST", "PUT", "DELETE", "PATCH"] diff --git a/package.json b/package.json index cc28ab1..446e3d3 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "moment": "2.30.1", "next": "15.1.3", "next-themes": "0.4.4", + "node-cache": "5.1.2", "node-fetch": "3.3.2", "nodemailer": "6.9.16", "oslo": "1.2.1", diff --git a/server/auth/sessions/app.ts b/server/auth/sessions/app.ts index ac91fc1..29c54ee 100644 --- a/server/auth/sessions/app.ts +++ b/server/auth/sessions/app.ts @@ -3,7 +3,13 @@ import { encodeHexLowerCase } from "@oslojs/encoding"; import { sha256 } from "@oslojs/crypto/sha2"; -import { Session, sessions, User, users } from "@server/db/schema"; +import { + resourceSessions, + Session, + sessions, + User, + users +} from "@server/db/schema"; import db from "@server/db"; import { eq } from "drizzle-orm"; import config from "@server/lib/config"; @@ -13,9 +19,14 @@ import logger from "@server/logger"; export const SESSION_COOKIE_NAME = config.getRawConfig().server.session_cookie_name; -export const SESSION_COOKIE_EXPIRES = 1000 * 60 * 60 * 24 * 30; +export const SESSION_COOKIE_EXPIRES = + 1000 * + 60 * + 60 * + config.getRawConfig().server.dashboard_session_length_hours; export const SECURE_COOKIES = config.getRawConfig().server.secure_cookies; -export const COOKIE_DOMAIN = "." + config.getBaseDomain(); +export const COOKIE_DOMAIN = + "." + new URL(config.getRawConfig().app.dashboard_url).hostname; export function generateSessionToken(): string { const bytes = new Uint8Array(20); @@ -65,12 +76,21 @@ export async function validateSessionToken( session.expiresAt = new Date( Date.now() + SESSION_COOKIE_EXPIRES ).getTime(); - await db - .update(sessions) - .set({ - expiresAt: session.expiresAt - }) - .where(eq(sessions.sessionId, session.sessionId)); + await db.transaction(async (trx) => { + await trx + .update(sessions) + .set({ + expiresAt: session.expiresAt + }) + .where(eq(sessions.sessionId, session.sessionId)); + + await trx + .update(resourceSessions) + .set({ + expiresAt: session.expiresAt + }) + .where(eq(resourceSessions.userSessionId, session.sessionId)); + }); } return { session, user }; } @@ -90,9 +110,9 @@ export function serializeSessionCookie( if (isSecure) { logger.debug("Setting cookie for secure origin"); if (SECURE_COOKIES) { - return `${SESSION_COOKIE_NAME}=${token}; HttpOnly; SameSite=Strict; Max-Age=${SESSION_COOKIE_EXPIRES}; Path=/; Secure; Domain=${COOKIE_DOMAIN}`; + return `${SESSION_COOKIE_NAME}=${token}; HttpOnly; SameSite=Strict; Max-Age=${SESSION_COOKIE_EXPIRES / 1000}; Path=/; Secure; Domain=${COOKIE_DOMAIN}`; } else { - return `${SESSION_COOKIE_NAME}=${token}; HttpOnly; SameSite=Strict; Max-Age=${SESSION_COOKIE_EXPIRES}; Path=/; Domain=${COOKIE_DOMAIN}`; + return `${SESSION_COOKIE_NAME}=${token}; HttpOnly; SameSite=Strict; Max-Age=${SESSION_COOKIE_EXPIRES / 1000}; Path=/; Domain=${COOKIE_DOMAIN}`; } } else { return `${SESSION_COOKIE_NAME}=${token}; HttpOnly; SameSite=Lax; Max-Age=${SESSION_COOKIE_EXPIRES}; Path=/;`; diff --git a/server/auth/sessions/resource.ts b/server/auth/sessions/resource.ts index e9b3241..e9dd9b9 100644 --- a/server/auth/sessions/resource.ts +++ b/server/auth/sessions/resource.ts @@ -6,19 +6,20 @@ import { eq, and } from "drizzle-orm"; import config from "@server/lib/config"; export const SESSION_COOKIE_NAME = - config.getRawConfig().server.resource_session_cookie_name; -export const SESSION_COOKIE_EXPIRES = 1000 * 60 * 60 * 24 * 30; + config.getRawConfig().server.session_cookie_name; +export const SESSION_COOKIE_EXPIRES = + 1000 * 60 * 60 * config.getRawConfig().server.resource_session_length_hours; export const SECURE_COOKIES = config.getRawConfig().server.secure_cookies; -export const COOKIE_DOMAIN = "." + config.getBaseDomain(); export async function createResourceSession(opts: { token: string; resourceId: number; - passwordId?: number; - pincodeId?: number; - whitelistId?: number; - accessTokenId?: string; - usedOtp?: boolean; + isRequestToken?: boolean; + passwordId?: number | null; + pincodeId?: number | null; + userSessionId?: string | null; + whitelistId?: number | null; + accessTokenId?: string | null; doNotExtend?: boolean; expiresAt?: number | null; sessionLength?: number | null; @@ -27,7 +28,8 @@ export async function createResourceSession(opts: { !opts.passwordId && !opts.pincodeId && !opts.whitelistId && - !opts.accessTokenId + !opts.accessTokenId && + !opts.userSessionId ) { throw new Error("Auth method must be provided"); } @@ -47,7 +49,9 @@ export async function createResourceSession(opts: { pincodeId: opts.pincodeId || null, whitelistId: opts.whitelistId || null, doNotExtend: opts.doNotExtend || false, - accessTokenId: opts.accessTokenId || null + accessTokenId: opts.accessTokenId || null, + isRequestToken: opts.isRequestToken || false, + userSessionId: opts.userSessionId || null }; await db.insert(resourceSessions).values(session); @@ -162,22 +166,25 @@ export async function invalidateAllSessions( export function serializeResourceSessionCookie( cookieName: string, - token: string + domain: string, + token: string, + isHttp: boolean = false ): string { - if (SECURE_COOKIES) { - return `${cookieName}=${token}; HttpOnly; SameSite=Strict; Max-Age=${SESSION_COOKIE_EXPIRES}; Path=/; Secure; Domain=${COOKIE_DOMAIN}`; + if (SECURE_COOKIES && !isHttp) { + return `${cookieName}_s=${token}; HttpOnly; SameSite=Strict; Max-Age=${SESSION_COOKIE_EXPIRES / 1000}; Path=/; Secure; Domain=${"." + domain}`; } else { - return `${cookieName}=${token}; HttpOnly; SameSite=Strict; Max-Age=${SESSION_COOKIE_EXPIRES}; Path=/; Domain=${COOKIE_DOMAIN}`; + return `${cookieName}=${token}; HttpOnly; SameSite=Strict; Max-Age=${SESSION_COOKIE_EXPIRES / 1000}; Path=/; Domain=${"." + domain}`; } } export function createBlankResourceSessionTokenCookie( - cookieName: string + cookieName: string, + domain: string ): string { if (SECURE_COOKIES) { - return `${cookieName}=; HttpOnly; SameSite=Strict; Max-Age=0; Path=/; Secure; Domain=${COOKIE_DOMAIN}`; + return `${cookieName}_s=; HttpOnly; SameSite=Strict; Max-Age=0; Path=/; Secure; Domain=${"." + domain}`; } else { - return `${cookieName}=; HttpOnly; SameSite=Strict; Max-Age=0; Path=/; Domain=${COOKIE_DOMAIN}`; + return `${cookieName}=; HttpOnly; SameSite=Strict; Max-Age=0; Path=/; Domain=${"." + domain}`; } } diff --git a/server/db/schema.ts b/server/db/schema.ts index fd9956b..acdda16 100644 --- a/server/db/schema.ts +++ b/server/db/schema.ts @@ -313,6 +313,10 @@ export const resourceSessions = sqliteTable("resourceSessions", { doNotExtend: integer("doNotExtend", { mode: "boolean" }) .notNull() .default(false), + isRequestToken: integer("isRequestToken", { mode: "boolean" }), + userSessionId: text("userSessionId").references(() => sessions.sessionId, { + onDelete: "cascade" + }), passwordId: integer("passwordId").references( () => resourcePassword.passwordId, { diff --git a/server/index.ts b/server/index.ts index 364db07..fd56f4e 100644 --- a/server/index.ts +++ b/server/index.ts @@ -2,7 +2,7 @@ import { runSetupFunctions } from "./setup"; import { createApiServer } from "./apiServer"; import { createNextServer } from "./nextServer"; import { createInternalServer } from "./internalServer"; -import { User, UserOrg } from "./db/schema"; +import { Session, User, UserOrg } from "./db/schema"; async function startServers() { await runSetupFunctions(); @@ -24,6 +24,7 @@ declare global { namespace Express { interface Request { user?: User; + session?: Session; userOrg?: UserOrg; userOrgRoleId?: number; userOrgId?: string; diff --git a/server/lib/config.ts b/server/lib/config.ts index 53dac62..2c1ef7f 100644 --- a/server/lib/config.ts +++ b/server/lib/config.ts @@ -61,8 +61,20 @@ const configSchema = z.object({ internal_hostname: z.string().transform((url) => url.toLowerCase()), secure_cookies: z.boolean(), session_cookie_name: z.string(), - resource_session_cookie_name: z.string(), resource_access_token_param: z.string(), + resource_session_request_param: z.string(), + dashboard_session_length_hours: z + .number() + .positive() + .gt(0) + .optional() + .default(720), + resource_session_length_hours: z + .number() + .positive() + .gt(0) + .optional() + .default(720), cors: z .object({ origins: z.array(z.string()).optional(), @@ -241,8 +253,6 @@ export class Config { : "false"; process.env.SESSION_COOKIE_NAME = parsedConfig.data.server.session_cookie_name; - process.env.RESOURCE_SESSION_COOKIE_NAME = - parsedConfig.data.server.resource_session_cookie_name; process.env.EMAIL_ENABLED = parsedConfig.data.email ? "true" : "false"; process.env.DISABLE_SIGNUP_WITHOUT_INVITE = parsedConfig.data.flags ?.disable_signup_without_invite @@ -254,6 +264,8 @@ export class Config { : "false"; process.env.RESOURCE_ACCESS_TOKEN_PARAM = parsedConfig.data.server.resource_access_token_param; + process.env.RESOURCE_SESSION_REQUEST_PARAM = + parsedConfig.data.server.resource_session_request_param; this.rawConfig = parsedConfig.data; } diff --git a/server/routers/auth/logout.ts b/server/routers/auth/logout.ts index 3b46676..e9e51a3 100644 --- a/server/routers/auth/logout.ts +++ b/server/routers/auth/logout.ts @@ -5,18 +5,17 @@ import response from "@server/lib/response"; import logger from "@server/logger"; import { createBlankSessionTokenCookie, - invalidateSession, - SESSION_COOKIE_NAME + invalidateSession } from "@server/auth/sessions/app"; +import { verifySession } from "@server/auth/sessions/verifySession"; export async function logout( req: Request, res: Response, next: NextFunction ): Promise { - const sessionId = req.cookies[SESSION_COOKIE_NAME]; - - if (!sessionId) { + const { user, session } = await verifySession(req); + if (!user || !session) { return next( createHttpError( HttpCode.BAD_REQUEST, @@ -26,7 +25,7 @@ export async function logout( } try { - await invalidateSession(sessionId); + await invalidateSession(session.sessionId); const isSecure = req.protocol === "https"; res.setHeader("Set-Cookie", createBlankSessionTokenCookie(isSecure)); diff --git a/server/routers/badger/exchangeSession.ts b/server/routers/badger/exchangeSession.ts new file mode 100644 index 0000000..eaf47e6 --- /dev/null +++ b/server/routers/badger/exchangeSession.ts @@ -0,0 +1,175 @@ +import HttpCode from "@server/types/HttpCode"; +import { NextFunction, Request, Response } from "express"; +import createHttpError from "http-errors"; +import { z } from "zod"; +import { fromError } from "zod-validation-error"; +import logger from "@server/logger"; +import { resourceAccessToken, resources, sessions } from "@server/db/schema"; +import db from "@server/db"; +import { eq } from "drizzle-orm"; +import { + createResourceSession, + serializeResourceSessionCookie, + validateResourceSessionToken +} from "@server/auth/sessions/resource"; +import { generateSessionToken } from "@server/auth"; +import { SESSION_COOKIE_EXPIRES } from "@server/auth/sessions/app"; +import { SESSION_COOKIE_EXPIRES as RESOURCE_SESSION_COOKIE_EXPIRES } from "@server/auth/sessions/resource"; +import config from "@server/lib/config"; +import { response } from "@server/lib"; + +const exchangeSessionBodySchema = z.object({ + requestToken: z.string(), + host: z.string() +}); + +export type ExchangeSessionBodySchema = z.infer< + typeof exchangeSessionBodySchema +>; + +export type ExchangeSessionResponse = { + valid: boolean; + cookie?: string; +}; + +export async function exchangeSession( + req: Request, + res: Response, + next: NextFunction +): Promise { + logger.debug("Exchange session: Badger sent", req.body); + + const parsedBody = exchangeSessionBodySchema.safeParse(req.body); + + if (!parsedBody.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedBody.error).toString() + ) + ); + } + + try { + const { requestToken, host } = parsedBody.data; + + const [resource] = await db + .select() + .from(resources) + .where(eq(resources.fullDomain, host)) + .limit(1); + + if (!resource) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Resource with host ${host} not found` + ) + ); + } + + const { resourceSession: requestSession } = + await validateResourceSessionToken( + requestToken, + resource.resourceId + ); + + if (!requestSession) { + return next( + createHttpError(HttpCode.UNAUTHORIZED, "Invalid request token") + ); + } + + if (!requestSession.isRequestToken) { + return next( + createHttpError(HttpCode.UNAUTHORIZED, "Invalid request token") + ); + } + + await db.delete(sessions).where(eq(sessions.sessionId, requestToken)); + + const token = generateSessionToken(); + + if (requestSession.userSessionId) { + const [res] = await db + .select() + .from(sessions) + .where(eq(sessions.sessionId, requestSession.userSessionId)) + .limit(1); + if (res) { + await createResourceSession({ + token, + resourceId: resource.resourceId, + isRequestToken: false, + userSessionId: requestSession.userSessionId, + doNotExtend: false, + expiresAt: res.expiresAt, + sessionLength: SESSION_COOKIE_EXPIRES + }); + } + } else if (requestSession.accessTokenId) { + const [res] = await db + .select() + .from(resourceAccessToken) + .where( + eq( + resourceAccessToken.accessTokenId, + requestSession.accessTokenId + ) + ) + .limit(1); + if (res) { + await createResourceSession({ + token, + resourceId: resource.resourceId, + isRequestToken: false, + accessTokenId: requestSession.accessTokenId, + doNotExtend: true, + expiresAt: res.expiresAt, + sessionLength: res.sessionLength + }); + } + } else { + await createResourceSession({ + token, + resourceId: resource.resourceId, + isRequestToken: false, + passwordId: requestSession.passwordId, + pincodeId: requestSession.pincodeId, + userSessionId: requestSession.userSessionId, + whitelistId: requestSession.whitelistId, + accessTokenId: requestSession.accessTokenId, + doNotExtend: false, + expiresAt: new Date( + Date.now() + SESSION_COOKIE_EXPIRES + ).getTime(), + sessionLength: RESOURCE_SESSION_COOKIE_EXPIRES + }); + } + + const cookieName = `${config.getRawConfig().server.session_cookie_name}`; + const cookie = serializeResourceSessionCookie( + cookieName, + resource.fullDomain, + token, + !resource.ssl + ); + + logger.debug(JSON.stringify("Exchange cookie: " + cookie)); + return response(res, { + data: { valid: true, cookie }, + success: true, + error: false, + message: "Session exchanged successfully", + status: HttpCode.OK + }); + } catch (e) { + console.error(e); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Failed to exchange session" + ) + ); + } +} diff --git a/server/routers/badger/index.ts b/server/routers/badger/index.ts index 7af4684..09f3a79 100644 --- a/server/routers/badger/index.ts +++ b/server/routers/badger/index.ts @@ -1 +1,2 @@ export * from "./verifySession"; +export * from "./exchangeSession"; diff --git a/server/routers/badger/verifySession.ts b/server/routers/badger/verifySession.ts index 0593e3b..ae6d2ed 100644 --- a/server/routers/badger/verifySession.ts +++ b/server/routers/badger/verifySession.ts @@ -4,17 +4,17 @@ import createHttpError from "http-errors"; import { z } from "zod"; import { fromError } from "zod-validation-error"; import { response } from "@server/lib/response"; -import { validateSessionToken } from "@server/auth/sessions/app"; import db from "@server/db"; import { ResourceAccessToken, - resourceAccessToken, + ResourcePassword, resourcePassword, + ResourcePincode, resourcePincode, resources, - resourceWhitelist, - User, - userOrgs + sessions, + userOrgs, + users } from "@server/db/schema"; import { and, eq } from "drizzle-orm"; import config from "@server/lib/config"; @@ -27,6 +27,12 @@ import { Resource, roleResources, userResources } from "@server/db/schema"; import logger from "@server/logger"; import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken"; import { generateSessionToken } from "@server/auth"; +import NodeCache from "node-cache"; + +// We'll see if this speeds anything up +const cache = new NodeCache({ + stdTTL: 5 // seconds +}); const verifyResourceSessionSchema = z.object({ sessions: z.record(z.string()).optional(), @@ -53,7 +59,7 @@ export async function verifyResourceSession( res: Response, next: NextFunction ): Promise { - logger.debug("Badger sent", req.body); // remove when done testing + logger.debug("Verify session: Badger sent", req.body); // remove when done testing const parsedBody = verifyResourceSessionSchema.safeParse(req.body); @@ -67,26 +73,52 @@ export async function verifyResourceSession( } try { - const { sessions, host, originalRequestURL, accessToken: token } = - parsedBody.data; + const { + sessions, + host, + originalRequestURL, + accessToken: token + } = parsedBody.data; - const [result] = await db - .select() - .from(resources) - .leftJoin( - resourcePincode, - eq(resourcePincode.resourceId, resources.resourceId) - ) - .leftJoin( - resourcePassword, - eq(resourcePassword.resourceId, resources.resourceId) - ) - .where(eq(resources.fullDomain, host)) - .limit(1); + const resourceCacheKey = `resource:${host}`; + let resourceData: + | { + resource: Resource | null; + pincode: ResourcePincode | null; + password: ResourcePassword | null; + } + | undefined = cache.get(resourceCacheKey); - const resource = result?.resources; - const pincode = result?.resourcePincode; - const password = result?.resourcePassword; + if (!resourceData) { + const [result] = await db + .select() + .from(resources) + .leftJoin( + resourcePincode, + eq(resourcePincode.resourceId, resources.resourceId) + ) + .leftJoin( + resourcePassword, + eq(resourcePassword.resourceId, resources.resourceId) + ) + .where(eq(resources.fullDomain, host)) + .limit(1); + + if (!result) { + logger.debug("Resource not found", host); + return notAllowed(res); + } + + resourceData = { + resource: result.resources, + pincode: result.resourcePincode, + password: result.resourcePassword + }; + + cache.set(resourceCacheKey, resourceData); + } + + const { resource, pincode, password } = resourceData; if (!resource) { logger.debug("Resource not found", host); @@ -145,37 +177,31 @@ export async function verifyResourceSession( return notAllowed(res); } - const sessionToken = - sessions[config.getRawConfig().server.session_cookie_name]; - - // check for unified login - if (sso && sessionToken) { - const { session, user } = await validateSessionToken(sessionToken); - if (session && user) { - const isAllowed = await isUserAllowedToAccessResource( - user, - resource - ); - - if (isAllowed) { - logger.debug( - "Resource allowed because user session is valid" - ); - return allowed(res); - } - } - } - const resourceSessionToken = sessions[ - `${config.getRawConfig().server.resource_session_cookie_name}_${resource.resourceId}` + `${config.getRawConfig().server.session_cookie_name}${resource.ssl ? "_s" : ""}` ]; if (resourceSessionToken) { - const { resourceSession } = await validateResourceSessionToken( - resourceSessionToken, - resource.resourceId - ); + const sessionCacheKey = `session:${resourceSessionToken}`; + let resourceSession: any = cache.get(sessionCacheKey); + + if (!resourceSession) { + const result = await validateResourceSessionToken( + resourceSessionToken, + resource.resourceId + ); + + resourceSession = result?.resourceSession; + cache.set(sessionCacheKey, resourceSession); + } + + if (resourceSession?.isRequestToken) { + logger.debug( + "Resource not allowed because session is a temporary request token" + ); + return notAllowed(res); + } if (resourceSession) { if (pincode && resourceSession.pincodeId) { @@ -208,6 +234,29 @@ export async function verifyResourceSession( ); return allowed(res); } + + if (resourceSession.userSessionId && sso) { + const userAccessCacheKey = `userAccess:${resourceSession.userSessionId}:${resource.resourceId}`; + + let isAllowed: boolean | undefined = + cache.get(userAccessCacheKey); + + if (isAllowed === undefined) { + isAllowed = await isUserAllowedToAccessResource( + resourceSession.userSessionId, + resource + ); + + cache.set(userAccessCacheKey, isAllowed); + } + + if (isAllowed) { + logger.debug( + "Resource allowed because user session is valid" + ); + return allowed(res); + } + } } } @@ -272,10 +321,15 @@ async function createAccessTokenSession( expiresAt: tokenItem.expiresAt, doNotExtend: tokenItem.expiresAt ? true : false }); - const cookieName = `${config.getRawConfig().server.resource_session_cookie_name}_${resource.resourceId}`; - const cookie = serializeResourceSessionCookie(cookieName, token); + const cookieName = `${config.getRawConfig().server.session_cookie_name}`; + const cookie = serializeResourceSessionCookie( + cookieName, + resource.fullDomain, + token, + !resource.ssl + ); res.appendHeader("Set-Cookie", cookie); - logger.debug("Access token is valid, creating new session") + logger.debug("Access token is valid, creating new session"); return response(res, { data: { valid: true }, success: true, @@ -286,9 +340,22 @@ async function createAccessTokenSession( } async function isUserAllowedToAccessResource( - user: User, + userSessionId: string, resource: Resource ): Promise { + const [res] = await db + .select() + .from(sessions) + .leftJoin(users, eq(users.userId, sessions.userId)) + .where(eq(sessions.sessionId, userSessionId)); + + const user = res.user; + const session = res.session; + + if (!user || !session) { + return false; + } + if ( config.getRawConfig().flags?.require_email_verification && !user.emailVerified diff --git a/server/routers/internal.ts b/server/routers/internal.ts index fb0e180..30f7fd6 100644 --- a/server/routers/internal.ts +++ b/server/routers/internal.ts @@ -3,7 +3,9 @@ import * as gerbil from "@server/routers/gerbil"; import * as badger from "@server/routers/badger"; import * as traefik from "@server/routers/traefik"; import * as auth from "@server/routers/auth"; +import * as resource from "@server/routers/resource"; import HttpCode from "@server/types/HttpCode"; +import { verifyResourceAccess, verifySessionUserMiddleware } from "@server/middlewares"; // Root routes const internalRouter = Router(); @@ -15,7 +17,14 @@ internalRouter.get("/", (_, res) => { internalRouter.get("/traefik-config", traefik.traefikConfigProvider); internalRouter.get( "/resource-session/:resourceId/:token", - auth.checkResourceSession, + auth.checkResourceSession +); + +internalRouter.post( + `/resource/:resourceId/get-exchange-token`, + verifySessionUserMiddleware, + verifyResourceAccess, + resource.getExchangeToken ); // Gerbil routes @@ -30,5 +39,6 @@ const badgerRouter = Router(); internalRouter.use("/badger", badgerRouter); badgerRouter.post("/verify-session", badger.verifyResourceSession); +badgerRouter.post("/exchange-session", badger.exchangeSession); export default internalRouter; diff --git a/server/routers/resource/authWithAccessToken.ts b/server/routers/resource/authWithAccessToken.ts index a4340f7..add5f27 100644 --- a/server/routers/resource/authWithAccessToken.ts +++ b/server/routers/resource/authWithAccessToken.ts @@ -1,18 +1,16 @@ import { generateSessionToken } from "@server/auth/sessions/app"; import db from "@server/db"; -import { resourceAccessToken, resources } from "@server/db/schema"; +import { resources } from "@server/db/schema"; import HttpCode from "@server/types/HttpCode"; import response from "@server/lib/response"; -import { eq, and } from "drizzle-orm"; +import { eq } from "drizzle-orm"; import { NextFunction, Request, Response } from "express"; import createHttpError from "http-errors"; import { z } from "zod"; import { fromError } from "zod-validation-error"; import { createResourceSession, - serializeResourceSessionCookie } from "@server/auth/sessions/resource"; -import config from "@server/lib/config"; import logger from "@server/logger"; import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken"; @@ -108,13 +106,11 @@ export async function authWithAccessToken( resourceId, token, accessTokenId: tokenItem.accessTokenId, - sessionLength: tokenItem.sessionLength, - expiresAt: tokenItem.expiresAt, - doNotExtend: tokenItem.expiresAt ? true : false + isRequestToken: true, + expiresAt: Date.now() + 1000 * 30, // 30 seconds + sessionLength: 1000 * 30, + doNotExtend: true }); - const cookieName = `${config.getRawConfig().server.resource_session_cookie_name}_${resource.resourceId}`; - const cookie = serializeResourceSessionCookie(cookieName, token); - res.appendHeader("Set-Cookie", cookie); return response(res, { data: { diff --git a/server/routers/resource/authWithPassword.ts b/server/routers/resource/authWithPassword.ts index dc7c41a..1aa5d63 100644 --- a/server/routers/resource/authWithPassword.ts +++ b/server/routers/resource/authWithPassword.ts @@ -11,9 +11,7 @@ import { z } from "zod"; import { fromError } from "zod-validation-error"; import { createResourceSession, - serializeResourceSessionCookie } from "@server/auth/sessions/resource"; -import config from "@server/lib/config"; import logger from "@server/logger"; import { verifyPassword } from "@server/auth/password"; @@ -120,11 +118,12 @@ export async function authWithPassword( await createResourceSession({ resourceId, token, - passwordId: definedPassword.passwordId + passwordId: definedPassword.passwordId, + isRequestToken: true, + expiresAt: Date.now() + 1000 * 30, // 30 seconds + sessionLength: 1000 * 30, + doNotExtend: true }); - const cookieName = `${config.getRawConfig().server.resource_session_cookie_name}_${resource.resourceId}`; - const cookie = serializeResourceSessionCookie(cookieName, token); - res.appendHeader("Set-Cookie", cookie); return response(res, { data: { diff --git a/server/routers/resource/authWithPincode.ts b/server/routers/resource/authWithPincode.ts index 97a0b13..6d83ba2 100644 --- a/server/routers/resource/authWithPincode.ts +++ b/server/routers/resource/authWithPincode.ts @@ -1,28 +1,21 @@ -import { verify } from "@node-rs/argon2"; import { generateSessionToken } from "@server/auth/sessions/app"; import db from "@server/db"; import { orgs, - resourceOtp, resourcePincode, resources, - resourceWhitelist } from "@server/db/schema"; import HttpCode from "@server/types/HttpCode"; import response from "@server/lib/response"; -import { and, eq } from "drizzle-orm"; +import { eq } from "drizzle-orm"; import { NextFunction, Request, Response } from "express"; import createHttpError from "http-errors"; import { z } from "zod"; import { fromError } from "zod-validation-error"; import { createResourceSession, - serializeResourceSessionCookie } from "@server/auth/sessions/resource"; import logger from "@server/logger"; -import config from "@server/lib/config"; -import { AuthWithPasswordResponse } from "./authWithPassword"; -import { isValidOtp, sendResourceOtpEmail } from "@server/auth/resourceOtp"; import { verifyPassword } from "@server/auth/password"; export const authWithPincodeBodySchema = z @@ -128,11 +121,12 @@ export async function authWithPincode( await createResourceSession({ resourceId, token, - pincodeId: definedPincode.pincodeId + pincodeId: definedPincode.pincodeId, + isRequestToken: true, + expiresAt: Date.now() + 1000 * 30, // 30 seconds + sessionLength: 1000 * 30, + doNotExtend: true }); - const cookieName = `${config.getRawConfig().server.resource_session_cookie_name}_${resource.resourceId}`; - const cookie = serializeResourceSessionCookie(cookieName, token); - res.appendHeader("Set-Cookie", cookie); return response(res, { data: { diff --git a/server/routers/resource/authWithWhitelist.ts b/server/routers/resource/authWithWhitelist.ts index 54c43f6..c58c7ef 100644 --- a/server/routers/resource/authWithWhitelist.ts +++ b/server/routers/resource/authWithWhitelist.ts @@ -3,7 +3,6 @@ import db from "@server/db"; import { orgs, resourceOtp, - resourcePassword, resources, resourceWhitelist } from "@server/db/schema"; @@ -16,9 +15,7 @@ import { z } from "zod"; import { fromError } from "zod-validation-error"; import { createResourceSession, - serializeResourceSessionCookie } from "@server/auth/sessions/resource"; -import config from "@server/lib/config"; import { isValidOtp, sendResourceOtpEmail } from "@server/auth/resourceOtp"; import logger from "@server/logger"; @@ -178,11 +175,12 @@ export async function authWithWhitelist( await createResourceSession({ resourceId, token, - whitelistId: whitelistedEmail.whitelistId + whitelistId: whitelistedEmail.whitelistId, + isRequestToken: true, + expiresAt: Date.now() + 1000 * 30, // 30 seconds + sessionLength: 1000 * 30, + doNotExtend: true }); - const cookieName = `${config.getRawConfig().server.resource_session_cookie_name}_${resource.resourceId}`; - const cookie = serializeResourceSessionCookie(cookieName, token); - res.appendHeader("Set-Cookie", cookie); return response(res, { data: { diff --git a/server/routers/resource/getExchangeToken.ts b/server/routers/resource/getExchangeToken.ts new file mode 100644 index 0000000..f80a083 --- /dev/null +++ b/server/routers/resource/getExchangeToken.ts @@ -0,0 +1,109 @@ +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { resources } from "@server/db/schema"; +import { eq } from "drizzle-orm"; +import { createResourceSession } from "@server/auth/sessions/resource"; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { fromError } from "zod-validation-error"; +import logger from "@server/logger"; +import { generateSessionToken } from "@server/auth/sessions/app"; +import config from "@server/lib/config"; +import { + encodeHexLowerCase +} from "@oslojs/encoding"; +import { sha256 } from "@oslojs/crypto/sha2"; +import { response } from "@server/lib"; + +const getExchangeTokenParams = z + .object({ + resourceId: z + .string() + .transform(Number) + .pipe(z.number().int().positive()) + }) + .strict(); + +export type GetExchangeTokenResponse = { + requestToken: string; +}; + +export async function getExchangeToken( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsedParams = getExchangeTokenParams.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedParams.error).toString() + ) + ); + } + + const { resourceId } = parsedParams.data; + + const resource = await db + .select() + .from(resources) + .where(eq(resources.resourceId, resourceId)) + .limit(1); + + if (resource.length === 0) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Resource with ID ${resourceId} not found` + ) + ); + } + + const ssoSession = + req.cookies[config.getRawConfig().server.session_cookie_name]; + if (!ssoSession) { + logger.debug(ssoSession); + return next( + createHttpError( + HttpCode.UNAUTHORIZED, + "Missing SSO session cookie" + ) + ); + } + + const sessionId = encodeHexLowerCase( + sha256(new TextEncoder().encode(ssoSession)) + ); + + const token = generateSessionToken(); + await createResourceSession({ + resourceId, + token, + userSessionId: sessionId, + isRequestToken: true, + expiresAt: Date.now() + 1000 * 30, // 30 seconds + sessionLength: 1000 * 30, + doNotExtend: true + }); + + logger.debug("Request token created successfully"); + + return response(res, { + data: { + requestToken: token + }, + success: true, + error: false, + message: "Request token created successfully", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + ); + } +} diff --git a/server/routers/resource/index.ts b/server/routers/resource/index.ts index 7dbee1b..ca06dfc 100644 --- a/server/routers/resource/index.ts +++ b/server/routers/resource/index.ts @@ -16,3 +16,4 @@ export * from "./setResourceWhitelist"; export * from "./getResourceWhitelist"; export * from "./authWithWhitelist"; export * from "./authWithAccessToken"; +export * from "./getExchangeToken"; diff --git a/server/routers/resource/updateResource.ts b/server/routers/resource/updateResource.ts index 67b6812..6a0e730 100644 --- a/server/routers/resource/updateResource.ts +++ b/server/routers/resource/updateResource.ts @@ -111,6 +111,10 @@ export async function updateResource( ); } + if (resource[0].resources.ssl !== updatedResource[0].ssl) { + // invalidate all sessions? + } + return response(res, { data: updatedResource[0], success: true, diff --git a/server/routers/traefik/getTraefikConfig.ts b/server/routers/traefik/getTraefikConfig.ts index 1153a53..bfc8640 100644 --- a/server/routers/traefik/getTraefikConfig.ts +++ b/server/routers/traefik/getTraefikConfig.ts @@ -39,7 +39,7 @@ export async function traefikConfigProvider( } const badgerMiddlewareName = "badger"; - const redirectMiddlewareName = "redirect-to-https"; + const redirectHttpsMiddlewareName = "redirect-to-https"; const http: any = { routers: {}, @@ -52,19 +52,18 @@ export async function traefikConfigProvider( "/api/v1", `http://${config.getRawConfig().server.internal_hostname}:${config.getRawConfig().server.internal_port}`, ).href, - resourceSessionCookieName: - config.getRawConfig().server.resource_session_cookie_name, userSessionCookieName: config.getRawConfig().server.session_cookie_name, accessTokenQueryParam: config.getRawConfig().server.resource_access_token_param, + resourceSessionRequestParam: config.getRawConfig().server.resource_session_request_param }, }, }, - [redirectMiddlewareName]: { + [redirectHttpsMiddlewareName]: { redirectScheme: { scheme: "https" }, - }, + } }, }; for (const item of all) { @@ -120,10 +119,9 @@ export async function traefikConfigProvider( }; if (resource.ssl) { - // this is a redirect router; all it does is redirect to the https version if tls is enabled http.routers![routerName + "-redirect"] = { entryPoints: [config.getRawConfig().traefik.http_entrypoint], - middlewares: [redirectMiddlewareName], + middlewares: [redirectHttpsMiddlewareName], service: serviceName, rule: `Host(\`${fullDomain}\`)`, }; diff --git a/server/setup/scripts/1.0.0-beta9.ts b/server/setup/scripts/1.0.0-beta9.ts index e4f6265..9cccd55 100644 --- a/server/setup/scripts/1.0.0-beta9.ts +++ b/server/setup/scripts/1.0.0-beta9.ts @@ -7,7 +7,13 @@ import { userInvites, users } from "@server/db/schema"; +import { APP_PATH, configFilePath1, configFilePath2 } from "@server/lib/consts"; import { sql } from "drizzle-orm"; +import fs from "fs"; +import yaml from "js-yaml"; +import path from "path"; +import { z } from "zod"; +import { fromZodError } from "zod-validation-error"; export default async function migration() { console.log("Running setup script 1.0.0-beta.9..."); @@ -32,5 +38,96 @@ export default async function migration() { console.error(error); } + try { + // Determine which config file exists + const filePaths = [configFilePath1, configFilePath2]; + let filePath = ""; + for (const path of filePaths) { + if (fs.existsSync(path)) { + filePath = path; + break; + } + } + + if (!filePath) { + throw new Error( + `No config file found (expected config.yml or config.yaml).` + ); + } + + // Read and parse the YAML file + let rawConfig: any; + const fileContents = fs.readFileSync(filePath, "utf8"); + rawConfig = yaml.load(fileContents); + + rawConfig.server.resource_session_request_param = "p_session_request"; + rawConfig.server.session_cookie_name = "p_session_token"; // rename to prevent conflicts + delete rawConfig.server.resource_session_cookie_name; + + // Write the updated YAML back to the file + const updatedYaml = yaml.dump(rawConfig); + fs.writeFileSync(filePath, updatedYaml, "utf8"); + } catch (e) { + console.log( + `Failed to add resource_session_request_param to config. Please add it manually. https://docs.fossorial.io/Pangolin/Configuration/config` + ); + throw e; + } + + try { + const traefikPath = path.join( + APP_PATH, + "traefik", + "traefik_config.yml" + ); + + const schema = z.object({ + experimental: z.object({ + plugins: z.object({ + badger: z.object({ + moduleName: z.string(), + version: z.string() + }) + }) + }) + }); + + const traefikFileContents = fs.readFileSync(traefikPath, "utf8"); + const traefikConfig = yaml.load(traefikFileContents) as any; + + const parsedConfig = schema.safeParse(traefikConfig); + + if (!parsedConfig.success) { + throw new Error(fromZodError(parsedConfig.error).toString()); + } + + traefikConfig.experimental.plugins.badger.version = "v1.0.0-beta.3"; + + const updatedTraefikYaml = yaml.dump(traefikConfig); + + fs.writeFileSync(traefikPath, updatedTraefikYaml, "utf8"); + + console.log( + "Updated the version of Badger in your Traefik configuration to v1.0.0-beta.3." + ); + } catch (e) { + console.log( + "We were unable to update the version of Badger in your Traefik configuration. Please update it manually." + ); + console.error(e); + } + + try { + await db.transaction(async (trx) => { + trx.run(sql`ALTER TABLE 'resourceSessions' ADD 'isRequestToken' integer;`); + trx.run(sql`ALTER TABLE 'resourceSessions' ADD 'userSessionId' text REFERENCES session(id);`); + }); + } catch (e) { + console.log( + "We were unable to add columns to the resourceSessions table." + ); + throw e; + } + console.log("Done."); } diff --git a/src/app/auth/resource/[resourceId]/AccessToken.tsx b/src/app/auth/resource/[resourceId]/AccessToken.tsx index 408a187..b70d75c 100644 --- a/src/app/auth/resource/[resourceId]/AccessToken.tsx +++ b/src/app/auth/resource/[resourceId]/AccessToken.tsx @@ -5,14 +5,12 @@ import { Button } from "@app/components/ui/button"; import { Card, CardContent, - CardFooter, CardHeader, CardTitle } from "@app/components/ui/card"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { AuthWithAccessTokenResponse } from "@server/routers/resource"; import { AxiosResponse } from "axios"; -import { Loader2 } from "lucide-react"; import Link from "next/link"; import { useEffect, useState } from "react"; @@ -32,7 +30,17 @@ export default function AccessToken({ const [loading, setLoading] = useState(true); const [isValid, setIsValid] = useState(false); - const api = createApiClient(useEnvContext()); + const { env } = useEnvContext(); + const api = createApiClient({ env }); + + function appendRequestToken(url: string, token: string) { + const fullUrl = new URL(url); + fullUrl.searchParams.append( + env.server.resourceSessionRequestParam, + token + ); + return fullUrl.toString(); + } useEffect(() => { if (!accessTokenId || !accessToken) { @@ -51,7 +59,10 @@ export default function AccessToken({ if (res.data.data.session) { setIsValid(true); - window.location.href = redirectUrl; + window.location.href = appendRequestToken( + redirectUrl, + res.data.data.session + ); } } catch (e) { console.error("Error checking access token", e); diff --git a/src/app/auth/resource/[resourceId]/ResourceAuthPortal.tsx b/src/app/auth/resource/[resourceId]/ResourceAuthPortal.tsx index c23403a..1aec64d 100644 --- a/src/app/auth/resource/[resourceId]/ResourceAuthPortal.tsx +++ b/src/app/auth/resource/[resourceId]/ResourceAuthPortal.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState, useSyncExternalStore } from "react"; +import { useState } from "react"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import * as z from "zod"; @@ -8,7 +8,6 @@ import { Card, CardContent, CardDescription, - CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; @@ -30,9 +29,6 @@ import { Key, User, Send, - ArrowLeft, - ArrowRight, - Lock, AtSign } from "lucide-react"; import { @@ -47,10 +43,8 @@ import { AxiosResponse } from "axios"; import LoginForm from "@app/components/LoginForm"; import { AuthWithPasswordResponse, - AuthWithAccessTokenResponse, AuthWithWhitelistResponse } from "@server/routers/resource"; -import { redirect } from "next/dist/server/api-utils"; import ResourceAccessDenied from "./ResourceAccessDenied"; import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; @@ -118,7 +112,9 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { const [otpState, setOtpState] = useState<"idle" | "otp_sent">("idle"); - const api = createApiClient(useEnvContext()); + const { env } = useEnvContext(); + + const api = createApiClient({ env }); function getDefaultSelectedMethod() { if (props.methods.sso) { @@ -169,6 +165,15 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { } }); + function appendRequestToken(url: string, token: string) { + const fullUrl = new URL(url); + fullUrl.searchParams.append( + env.server.resourceSessionRequestParam, + token + ); + return fullUrl.toString(); + } + const onWhitelistSubmit = (values: any) => { setLoadingLogin(true); api.post>( @@ -190,7 +195,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { const session = res.data.data.session; if (session) { - window.location.href = props.redirect; + window.location.href = appendRequestToken(props.redirect, session); } }) .catch((e) => { @@ -212,7 +217,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { setPincodeError(null); const session = res.data.data.session; if (session) { - window.location.href = props.redirect; + window.location.href = appendRequestToken(props.redirect, session); } }) .catch((e) => { @@ -237,7 +242,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { setPasswordError(null); const session = res.data.data.session; if (session) { - window.location.href = props.redirect; + window.location.href = appendRequestToken(props.redirect, session); } }) .catch((e) => { @@ -619,16 +624,6 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { - {/* {activeTab === "sso" && ( -
-

- Don't have an account?{" "} - - Sign up - -

-
- )} */} ) : ( diff --git a/src/app/auth/resource/[resourceId]/page.tsx b/src/app/auth/resource/[resourceId]/page.tsx index 49041a0..cc576a0 100644 --- a/src/app/auth/resource/[resourceId]/page.tsx +++ b/src/app/auth/resource/[resourceId]/page.tsx @@ -1,7 +1,6 @@ import { - AuthWithAccessTokenResponse, GetResourceAuthInfoResponse, - GetResourceResponse + GetExchangeTokenResponse } from "@server/routers/resource"; import ResourceAuthPortal from "./ResourceAuthPortal"; import { internal, priv } from "@app/lib/api"; @@ -12,9 +11,6 @@ import { verifySession } from "@app/lib/auth/verifySession"; import { redirect } from "next/navigation"; import ResourceNotFound from "./ResourceNotFound"; import ResourceAccessDenied from "./ResourceAccessDenied"; -import { cookies } from "next/headers"; -import { CheckResourceSessionResponse } from "@server/routers/auth"; -import AccessTokenInvalid from "./AccessToken"; import AccessToken from "./AccessToken"; import { pullEnv } from "@app/lib/pullEnv"; @@ -48,7 +44,7 @@ export default async function ResourceAuthPage(props: { // TODO: fix this return (
- {/* @ts-ignore */} + {/* @ts-ignore */}
); @@ -83,49 +79,41 @@ export default async function ResourceAuthPage(props: { ); } - const allCookies = await cookies(); - const cookieName = - env.server.resourceSessionCookieName + `_${params.resourceId}`; - const sessionId = allCookies.get(cookieName)?.value ?? null; - - if (sessionId) { - let doRedirect = false; - try { - const res = await priv.get< - AxiosResponse - >(`/resource-session/${params.resourceId}/${sessionId}`); - - if (res && res.data.data.valid) { - doRedirect = true; - } - } catch (e) {} - - if (doRedirect) { - redirect(redirectUrl); - } - } - if (!hasAuth) { // no authentication so always go straight to the resource redirect(redirectUrl); } + + // convert the dashboard token into a resource session token let userIsUnauthorized = false; if (user && authInfo.sso) { - let doRedirect = false; + let redirectToUrl: string | undefined; try { - const res = await internal.get>( - `/resource/${params.resourceId}`, + const res = await priv.post< + AxiosResponse + >( + `/resource/${params.resourceId}/get-exchange-token`, + {}, await authCookieHeader() ); - doRedirect = true; + if (res.data.data.requestToken) { + const paramName = env.server.resourceSessionRequestParam; + // append the param with the token to the redirect url + const fullUrl = new URL(redirectUrl); + fullUrl.searchParams.append( + paramName, + res.data.data.requestToken + ); + redirectToUrl = fullUrl.toString(); + } } catch (e) { userIsUnauthorized = true; } - if (doRedirect) { - redirect(redirectUrl); + if (redirectToUrl) { + redirect(redirectToUrl); } } diff --git a/src/lib/pullEnv.ts b/src/lib/pullEnv.ts index d335d70..564ba0b 100644 --- a/src/lib/pullEnv.ts +++ b/src/lib/pullEnv.ts @@ -6,8 +6,8 @@ export function pullEnv(): Env { nextPort: process.env.NEXT_PORT as string, externalPort: process.env.SERVER_EXTERNAL_PORT as string, sessionCookieName: process.env.SESSION_COOKIE_NAME as string, - resourceSessionCookieName: process.env.RESOURCE_SESSION_COOKIE_NAME as string, - resourceAccessTokenParam: process.env.RESOURCE_ACCESS_TOKEN_PARAM as string + resourceAccessTokenParam: process.env.RESOURCE_ACCESS_TOKEN_PARAM as string, + resourceSessionRequestParam: process.env.RESOURCE_SESSION_REQUEST_PARAM as string }, app: { environment: process.env.ENVIRONMENT as string, diff --git a/src/lib/types/env.ts b/src/lib/types/env.ts index 559bb53..9a2e5b9 100644 --- a/src/lib/types/env.ts +++ b/src/lib/types/env.ts @@ -7,8 +7,8 @@ export type Env = { externalPort: string; nextPort: string; sessionCookieName: string; - resourceSessionCookieName: string; resourceAccessTokenParam: string; + resourceSessionRequestParam: string; }, email: { emailEnabled: boolean;