fix: prevent cookie manager from racing env init

this fixes a bug where using LOAD_ENV_FILE did not work with COOKIE_SECRET
This commit is contained in:
Aarnav Tale
2025-01-06 08:11:45 +05:30
parent a4bb3cce5f
commit 7217659720
15 changed files with 97 additions and 46 deletions
+22 -4
View File
@@ -8,10 +8,11 @@ import { resolve } from 'node:path';
import { parse } from 'yaml';
import { type IntegrationFactory, loadIntegration } from '~/integration';
import { type HeadscaleConfig, loadConfig } from '~/utils/config/headscale';
import { IntegrationFactory, loadIntegration } from '~/integration';
import { HeadscaleConfig, loadConfig } from '~/utils/config/headscale';
import { testOidc } from '~/utils/oidc';
import log from '~/utils/log';
import { initSessionManager } from '~/utils/sessions.server';
export interface HeadplaneContext {
debug: boolean;
@@ -36,12 +37,25 @@ export interface HeadplaneContext {
}
let context: HeadplaneContext | undefined;
let loadLock = false;
export async function loadContext(): Promise<HeadplaneContext> {
if (context) {
return context;
}
if (loadLock) {
return new Promise((resolve) => {
const interval = setInterval(() => {
if (context) {
clearInterval(interval);
resolve(context);
}
}, 100);
});
}
loadLock = true;
const envFile = process.env.LOAD_ENV_FILE === 'true';
if (envFile) {
log.info('CTXT', 'Loading environment variables from .env');
@@ -68,7 +82,7 @@ export async function loadContext(): Promise<HeadplaneContext> {
headscaleUrl = headscaleUrl ?? config.server_url;
if (!headscalePublicUrl) {
// Fallback to the config value if the env var is not set
headscalePublicUrl = config.public_url;
headscalePublicUrl = config.server_url;
}
}
@@ -81,6 +95,9 @@ export async function loadContext(): Promise<HeadplaneContext> {
throw new Error('COOKIE_SECRET not set');
}
// Initialize Session Management
initSessionManager();
context = {
debug,
headscaleUrl,
@@ -107,6 +124,7 @@ export async function loadContext(): Promise<HeadplaneContext> {
);
log.info('CTXT', 'OIDC: %s', context.oidc ? 'Configured' : 'Unavailable');
loadLock = false;
return context;
}
@@ -235,7 +253,7 @@ async function checkOidc(config?: HeadscaleConfig) {
return;
}
if (config.oidc.only_start_if_oidc_is_available) {
if (config?.oidc?.only_start_if_oidc_is_available) {
log.debug('CTXT', 'Validating OIDC configuration from headscale config');
const result = await testOidc(issuer, client, secret);
if (!result) {
+1 -1
View File
@@ -16,7 +16,7 @@ import {
} from 'oauth4webapi';
import { post } from '~/utils/headscale';
import { commitSession, getSession } from '~/utils/sessions';
import { commitSession, getSession } from '~/utils/sessions.server';
import log from '~/utils/log';
import type { HeadplaneContext } from './config/headplane';
+62
View File
@@ -0,0 +1,62 @@
import { Session, SessionStorage, createCookieSessionStorage } from 'react-router';
export type SessionData = {
hsApiKey: string;
authState: string;
authNonce: string;
authVerifier: string;
user: {
name: string;
email?: string;
};
};
type SessionFlashData = {
error: string;
};
type SessionStore = SessionStorage<SessionData, SessionFlashData>;
// TODO: Add args to this function to allow custom domain/config
let sessionStorage: SessionStore | null = null;
export function initSessionManager() {
if (sessionStorage) {
throw new Error('Session manager already initialized');
}
sessionStorage = createCookieSessionStorage<SessionData, SessionFlashData>({
cookie: {
name: 'hp_sess',
httpOnly: true,
maxAge: 60 * 60 * 24, // 24 hours
path: '/',
sameSite: 'lax',
secrets: [process.env.COOKIE_SECRET!],
secure: process.env.COOKIE_SECURE !== 'false',
},
});
}
export function getSession(cookie: string | null) {
if (!sessionStorage) {
throw new Error('Session manager not initialized');
}
return sessionStorage.getSession(cookie);
}
export function destroySession(session: Session) {
if (!sessionStorage) {
throw new Error('Session manager not initialized');
}
return sessionStorage.destroySession(session);
}
export function commitSession(session: Session, opts?: { maxAge?: number }) {
if (!sessionStorage) {
throw new Error('Session manager not initialized');
}
return sessionStorage.commitSession(session, opts);
}
-29
View File
@@ -1,29 +0,0 @@
import { createCookieSessionStorage } from 'react-router'; // Or cloudflare/deno
export type SessionData = {
hsApiKey: string;
authState: string;
authNonce: string;
authVerifier: string;
user: {
name: string;
email?: string;
};
};
type SessionFlashData = {
error: string;
};
export const { getSession, commitSession, destroySession } =
createCookieSessionStorage<SessionData, SessionFlashData>({
cookie: {
name: 'hp_sess',
httpOnly: true,
maxAge: 60 * 60 * 24, // 24 hours
path: '/',
sameSite: 'lax',
secrets: [process.env.COOKIE_SECRET!],
secure: process.env.COOKIE_SECURE !== 'false',
},
});