mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-19 08:28:10 +02:00
feat: enable stack auth config from backend
This commit is contained in:
parent
788ff94cec
commit
f586aebe5b
13 changed files with 261 additions and 53 deletions
|
|
@ -30,6 +30,12 @@ CORS_ALLOWED_ORIGINS = [
|
|||
o.strip() for o in os.getenv("CORS_ALLOWED_ORIGINS", "").split(",") if o.strip()
|
||||
]
|
||||
AUTH_PROVIDER = os.getenv("AUTH_PROVIDER", "local")
|
||||
# Stack Auth public client config. These are safe to expose to the browser (the
|
||||
# publishable client key is public by design, and the project id is non-sensitive),
|
||||
# and are served to the UI at runtime via /api/v1/health so the frontend no longer
|
||||
# needs them baked into the bundle at build time.
|
||||
STACK_AUTH_PROJECT_ID = os.getenv("STACK_AUTH_PROJECT_ID")
|
||||
STACK_PUBLISHABLE_CLIENT_KEY = os.getenv("STACK_PUBLISHABLE_CLIENT_KEY")
|
||||
DOGRAH_MPS_SECRET_KEY = os.getenv("DOGRAH_MPS_SECRET_KEY", None)
|
||||
MPS_API_URL = os.getenv("MPS_API_URL", "https://services.dograh.com")
|
||||
|
||||
|
|
|
|||
|
|
@ -72,6 +72,11 @@ class HealthResponse(BaseModel):
|
|||
auth_provider: str
|
||||
turn_enabled: bool
|
||||
force_turn_relay: bool
|
||||
# Public Stack Auth client config — only populated when auth_provider == "stack".
|
||||
# The UI reads these at runtime to initialize Stack, so they no longer need to
|
||||
# be baked into the browser bundle at build time. Both are public values.
|
||||
stack_project_id: str | None = None
|
||||
stack_publishable_client_key: str | None = None
|
||||
|
||||
|
||||
@router.get("/health", response_model=HealthResponse)
|
||||
|
|
@ -81,12 +86,15 @@ async def health() -> HealthResponse:
|
|||
AUTH_PROVIDER,
|
||||
DEPLOYMENT_MODE,
|
||||
FORCE_TURN_RELAY,
|
||||
STACK_AUTH_PROJECT_ID,
|
||||
STACK_PUBLISHABLE_CLIENT_KEY,
|
||||
TURN_SECRET,
|
||||
)
|
||||
from api.utils.common import get_backend_endpoints
|
||||
|
||||
logger.debug("Health endpoint called")
|
||||
backend_endpoint, _ = await get_backend_endpoints()
|
||||
is_stack = AUTH_PROVIDER == "stack"
|
||||
return HealthResponse(
|
||||
status="ok",
|
||||
version=APP_VERSION,
|
||||
|
|
@ -95,4 +103,8 @@ async def health() -> HealthResponse:
|
|||
auth_provider=AUTH_PROVIDER,
|
||||
turn_enabled=bool(TURN_SECRET),
|
||||
force_turn_relay=FORCE_TURN_RELAY,
|
||||
stack_project_id=STACK_AUTH_PROJECT_ID if is_stack else None,
|
||||
stack_publishable_client_key=(
|
||||
STACK_PUBLISHABLE_CLIENT_KEY if is_stack else None
|
||||
),
|
||||
)
|
||||
|
|
|
|||
99
docs/deployment/authentication.mdx
Normal file
99
docs/deployment/authentication.mdx
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
---
|
||||
title: "Authentication"
|
||||
description: "Configure how self-hosted Dograh authenticates users — the default local provider, or Stack Auth for social login"
|
||||
---
|
||||
|
||||
Self-hosted Dograh ships with a built-in **local** authentication provider (email + password, backed by a signed JWT). This is the default and needs no external service.
|
||||
|
||||
To offer social logins (Google, GitHub, and others), you can delegate sign-in to **[Stack Auth](https://stack-auth.com)**. Enabling it is a **runtime** configuration change — set a few environment variables and restart. The prebuilt `dograhai/dograh-api` and `dograhai/dograh-ui` images work as-is; you do **not** need to rebuild or build from source.
|
||||
|
||||
<Note>
|
||||
The active provider is controlled by the backend `AUTH_PROVIDER` variable (`local` by default). The frontend discovers the provider — and, for Stack, its public client config — at runtime from the backend's `/api/v1/health` response, so the browser bundle never needs Stack values baked in at build time.
|
||||
</Note>
|
||||
|
||||
## How it works
|
||||
|
||||
1. The backend reads `AUTH_PROVIDER` and the Stack settings from its environment.
|
||||
2. When `AUTH_PROVIDER=stack`, `/api/v1/health` returns the **public** Stack client config (project id + publishable client key).
|
||||
3. The UI fetches that at runtime and initializes the Stack SDK in the browser.
|
||||
4. The **secret server key** is used only server-side (by the backend and the UI's server runtime) and is never sent to the browser.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
A Stack Auth project. Create one in the [Stack Auth dashboard](https://app.stack-auth.com) and configure the social login providers you want to offer.
|
||||
|
||||
## Step 1 — Collect your Stack credentials
|
||||
|
||||
From your project in the [Stack Auth dashboard](https://app.stack-auth.com), gather:
|
||||
|
||||
| Value | Sensitivity |
|
||||
|---|---|
|
||||
| **Project ID** | Public |
|
||||
| **Publishable client key** | Public (safe to expose in the browser) |
|
||||
| **Secret server key** | Secret — keep server-side only |
|
||||
| **API base URL** | Public. For Stack's hosted service this is `https://api.stack-auth.com` |
|
||||
|
||||
## Step 2 — Configure the backend (`api`)
|
||||
|
||||
Set these on the `api` service. Add them to the `environment:` block of the `api` service in your `docker-compose.yaml`:
|
||||
|
||||
```yaml docker-compose.yaml
|
||||
services:
|
||||
api:
|
||||
environment:
|
||||
AUTH_PROVIDER: "stack"
|
||||
STACK_AUTH_PROJECT_ID: "<your-project-id>"
|
||||
STACK_PUBLISHABLE_CLIENT_KEY: "<your-publishable-client-key>"
|
||||
STACK_SECRET_SERVER_KEY: "<your-secret-server-key>"
|
||||
STACK_AUTH_API_URL: "https://api.stack-auth.com"
|
||||
```
|
||||
|
||||
## Step 3 — Configure the UI (`ui`)
|
||||
|
||||
The UI runs server-side code (SSR pages and the `/handler/*` auth routes) that calls Stack with the secret server key, so the `ui` service needs that one value too:
|
||||
|
||||
```yaml docker-compose.yaml
|
||||
services:
|
||||
ui:
|
||||
environment:
|
||||
STACK_SECRET_SERVER_KEY: "<your-secret-server-key>"
|
||||
```
|
||||
|
||||
<Note>
|
||||
The `ui` service does **not** need the project id or publishable client key — it receives those from the backend at runtime via `/api/v1/health`. Only the secret server key (used server-side) is set here.
|
||||
</Note>
|
||||
|
||||
## Step 4 — Restart and verify
|
||||
|
||||
Recreate the containers so they pick up the new environment:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Confirm the backend reports the active provider and the public client config:
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:8000/api/v1/health
|
||||
# expect: "auth_provider":"stack", plus "stack_project_id" and "stack_publishable_client_key"
|
||||
```
|
||||
|
||||
Then open the UI. The sign-in page should now present your configured Stack Auth social login options instead of the local email/password form.
|
||||
|
||||
## Environment variable reference
|
||||
|
||||
| Variable | Service | Secret | Notes |
|
||||
|---|---|---|---|
|
||||
| `AUTH_PROVIDER` | `api` | — | Set to `stack` (default `local`) |
|
||||
| `STACK_AUTH_PROJECT_ID` | `api` | No | Stack project ID; served to the UI at runtime |
|
||||
| `STACK_PUBLISHABLE_CLIENT_KEY` | `api` | No | Publishable key; served to the UI at runtime |
|
||||
| `STACK_SECRET_SERVER_KEY` | `api` + `ui` | **Yes** | Server-side only — never exposed to the browser |
|
||||
| `STACK_AUTH_API_URL` | `api` | No | Stack REST API base URL |
|
||||
|
||||
<Warning>
|
||||
`STACK_SECRET_SERVER_KEY` is the only secret here. Keep it out of any client-visible config and never bake it into an image. The project ID and publishable client key are public by design — the backend deliberately serves them to the browser so Stack can initialize at runtime.
|
||||
</Warning>
|
||||
|
||||
## Reverting to local auth
|
||||
|
||||
Remove the variables above (or set `AUTH_PROVIDER=local`) and restart. The UI detects `local` from the backend at runtime and falls back to the built-in email/password flow — no rebuild required.
|
||||
|
|
@ -22,7 +22,7 @@ The relevant required variables for each mode are noted in the descriptions belo
|
|||
|---|---|---|
|
||||
| `ENVIRONMENT` | `local` | Runtime environment. Affects logging and behaviour. One of `local`, `production`, `test` |
|
||||
| `DEPLOYMENT_MODE` | `oss` | Deployment mode. Use `oss` for self-hosted |
|
||||
| `AUTH_PROVIDER` | `local` | Authentication provider. Use `local` for OSS |
|
||||
| `AUTH_PROVIDER` | `local` | Authentication provider. `local` (default) uses the built-in email/password flow. Set to `stack` to delegate to Stack Auth for social login — see [Authentication](/deployment/authentication) for the full setup |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -48,6 +48,19 @@ Never use the placeholder `OSS_JWT_SECRET` in a production deployment. Generate
|
|||
|
||||
---
|
||||
|
||||
## Authentication (Stack Auth)
|
||||
|
||||
Set these when `AUTH_PROVIDER=stack` to delegate sign-in to [Stack Auth](https://stack-auth.com) for social login. The project id and publishable client key are public and are served to the browser at runtime via `/api/v1/health`; the secret server key stays server-side. See [Authentication](/deployment/authentication) for the full walkthrough.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `STACK_AUTH_PROJECT_ID` | `null` | **Required for `stack`.** Stack project ID (public) |
|
||||
| `STACK_PUBLISHABLE_CLIENT_KEY` | `null` | **Required for `stack`.** Stack publishable client key (public) |
|
||||
| `STACK_SECRET_SERVER_KEY` | `null` | **Required for `stack`.** Stack secret server key — server-side only, also set on the `ui` service. Keep secret |
|
||||
| `STACK_AUTH_API_URL` | `null` | **Required for `stack`.** Stack REST API base URL (e.g. `https://api.stack-auth.com`) |
|
||||
|
||||
---
|
||||
|
||||
## URLs
|
||||
|
||||
| Variable | Default | Description |
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@
|
|||
"pages": [
|
||||
"deployment/introduction",
|
||||
"deployment/docker",
|
||||
"deployment/authentication",
|
||||
"deployment/custom-domain",
|
||||
"deployment/scaling",
|
||||
"deployment/update",
|
||||
|
|
|
|||
|
|
@ -42,15 +42,6 @@ ENV NEXT_PUBLIC_CHATWOOT_URL="https://chat.dograh.com"
|
|||
ENV NEXT_PUBLIC_CHATWOOT_TOKEN="3fkFx2mCEjNHjM9gaNc4A82X"
|
||||
ENV BACKEND_URL="http://api:8000"
|
||||
|
||||
# Stack Auth (optional, for self-hosted social login). NEXT_PUBLIC_* values must
|
||||
# be set at build time so Next.js inlines them into the browser bundle; setting
|
||||
# them as runtime container env has no effect. Unset by default (empty strings),
|
||||
# which leaves the official image on the built-in local auth flow.
|
||||
ARG NEXT_PUBLIC_STACK_PROJECT_ID
|
||||
ARG NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY
|
||||
ENV NEXT_PUBLIC_STACK_PROJECT_ID=$NEXT_PUBLIC_STACK_PROJECT_ID
|
||||
ENV NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=$NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY
|
||||
|
||||
# Build the application with standalone mode
|
||||
# Increase Node.js heap size to prevent out-of-memory errors during build
|
||||
ENV NODE_OPTIONS="--max-old-space-size=4096"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,17 @@
|
|||
import { NextResponse } from 'next/server';
|
||||
|
||||
import { getAuthProvider } from '@/lib/auth/config';
|
||||
import { getAuthProvider, getStackConfig } from '@/lib/auth/config';
|
||||
import logger from '@/lib/logger';
|
||||
|
||||
export async function GET() {
|
||||
const provider = await getAuthProvider();
|
||||
// When using Stack, hand the public client config to the browser so it can
|
||||
// initialize the Stack SDK at runtime (no build-time NEXT_PUBLIC_* needed).
|
||||
const stackConfig = provider === 'stack' ? await getStackConfig() : null;
|
||||
logger.debug(`Got provider ${provider} from getAuthProvider`)
|
||||
return NextResponse.json({ provider });
|
||||
return NextResponse.json({
|
||||
provider,
|
||||
stackProjectId: stackConfig?.projectId ?? null,
|
||||
stackPublishableClientKey: stackConfig?.publishableClientKey ?? null,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
import { getStackConfig } from "@/lib/auth/config";
|
||||
|
||||
/**
|
||||
* Helper route that receives a refresh token via query parameters, stores it as
|
||||
* the regular Stack cookie *for the current sub-domain only* and finally
|
||||
|
|
@ -18,6 +20,13 @@ export async function GET(request: NextRequest) {
|
|||
return new Response("Missing refresh_token", { status: 400 });
|
||||
}
|
||||
|
||||
// The Stack session cookie is named `stack-refresh-<projectId>`. The project
|
||||
// id comes from the backend at runtime, so no inlined NEXT_PUBLIC_* is needed.
|
||||
const stackConfig = await getStackConfig();
|
||||
if (!stackConfig) {
|
||||
return new Response("Stack auth is not configured", { status: 400 });
|
||||
}
|
||||
|
||||
// Prepare redirect – if the supplied redirect path is an absolute URL we use
|
||||
// it as-is, otherwise we resolve it relative to the current request.
|
||||
const redirectUrl = redirectPath.startsWith("http")
|
||||
|
|
@ -32,7 +41,7 @@ export async function GET(request: NextRequest) {
|
|||
// Store the refresh token cookie without an explicit domain so that it is
|
||||
// scoped to the current (sub-)domain. This avoids collisions between the
|
||||
// admin (superadmin.*) and the regular app (app.*) domains.
|
||||
response.cookies.set(`stack-refresh-${process.env.NEXT_PUBLIC_STACK_PROJECT_ID}` as string, refreshToken, {
|
||||
response.cookies.set(`stack-refresh-${stackConfig.projectId}`, refreshToken, {
|
||||
path: "/",
|
||||
maxAge,
|
||||
secure: true,
|
||||
|
|
|
|||
|
|
@ -2,15 +2,29 @@ import "server-only";
|
|||
|
||||
import { getServerBackendUrl } from "@/lib/apiClient";
|
||||
|
||||
let cachedAuthProvider: string | null = null;
|
||||
export interface StackConfig {
|
||||
projectId: string;
|
||||
publishableClientKey: string;
|
||||
}
|
||||
|
||||
interface ResolvedAuthConfig {
|
||||
authProvider: string;
|
||||
stackConfig: StackConfig | null;
|
||||
}
|
||||
|
||||
let cachedConfig: ResolvedAuthConfig | null = null;
|
||||
|
||||
/**
|
||||
* Fetches the auth provider from the backend health endpoint and caches it.
|
||||
* Falls back to 'local' on error.
|
||||
* Fetches the auth configuration from the backend health endpoint and caches it.
|
||||
*
|
||||
* The backend reports the active auth provider and — when it is `stack` — the
|
||||
* public Stack client config (project id + publishable client key). The UI uses
|
||||
* these at runtime to initialize Stack Auth, so they no longer need to be baked
|
||||
* into the browser bundle at build time. Falls back to local auth on error.
|
||||
*/
|
||||
export async function getAuthProvider(): Promise<string> {
|
||||
if (cachedAuthProvider) {
|
||||
return cachedAuthProvider;
|
||||
async function resolveAuthConfig(): Promise<ResolvedAuthConfig> {
|
||||
if (cachedConfig) {
|
||||
return cachedConfig;
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -20,13 +34,39 @@ export async function getAuthProvider(): Promise<string> {
|
|||
});
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
cachedAuthProvider = (data.auth_provider as string) || "local";
|
||||
return cachedAuthProvider;
|
||||
const authProvider = (data.auth_provider as string) || "local";
|
||||
const stackConfig =
|
||||
authProvider === "stack" &&
|
||||
data.stack_project_id &&
|
||||
data.stack_publishable_client_key
|
||||
? {
|
||||
projectId: data.stack_project_id as string,
|
||||
publishableClientKey:
|
||||
data.stack_publishable_client_key as string,
|
||||
}
|
||||
: null;
|
||||
cachedConfig = { authProvider, stackConfig };
|
||||
return cachedConfig;
|
||||
}
|
||||
} catch {
|
||||
// Backend not reachable — fall back to local
|
||||
}
|
||||
|
||||
cachedAuthProvider = "local";
|
||||
return cachedAuthProvider;
|
||||
cachedConfig = { authProvider: "local", stackConfig: null };
|
||||
return cachedConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the active auth provider ('local' or 'stack'). Falls back to 'local'.
|
||||
*/
|
||||
export async function getAuthProvider(): Promise<string> {
|
||||
return (await resolveAuthConfig()).authProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the public Stack client config when the active provider is `stack`,
|
||||
* otherwise null. Server-only — the browser receives these via /api/config/auth.
|
||||
*/
|
||||
export async function getStackConfig(): Promise<StackConfig | null> {
|
||||
return (await resolveAuthConfig()).stackConfig;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,31 +42,57 @@ const LoadingFallback = (
|
|||
</div>
|
||||
);
|
||||
|
||||
interface ResolvedAuthConfig {
|
||||
provider: string;
|
||||
// Public Stack client config, fetched from the backend at runtime. Null unless
|
||||
// the provider is 'stack' and the backend supplied both values.
|
||||
stack: { projectId: string; publishableClientKey: string } | null;
|
||||
}
|
||||
|
||||
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const [authProvider, setAuthProvider] = useState<string | null>(null);
|
||||
const [config, setConfig] = useState<ResolvedAuthConfig | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/config/auth')
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
logger.debug(`Setting auth provider as ${data.provider}`)
|
||||
setAuthProvider(data.provider || 'stack')
|
||||
})
|
||||
setConfig({
|
||||
provider: data.provider || 'local',
|
||||
stack:
|
||||
data.stackProjectId && data.stackPublishableClientKey
|
||||
? {
|
||||
projectId: data.stackProjectId,
|
||||
publishableClientKey: data.stackPublishableClientKey,
|
||||
}
|
||||
: null,
|
||||
})
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error(`Got error ${e} while setting auth provider`)
|
||||
setAuthProvider('local')
|
||||
setConfig({ provider: 'local', stack: null })
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (!authProvider) {
|
||||
if (!config) {
|
||||
return LoadingFallback;
|
||||
}
|
||||
|
||||
// For Stack provider, use the dedicated wrapper
|
||||
if (authProvider === 'stack') {
|
||||
if (config.provider === 'stack') {
|
||||
if (!config.stack) {
|
||||
logger.error(
|
||||
'Auth provider is "stack" but the backend returned no Stack client config. ' +
|
||||
'Ensure STACK_AUTH_PROJECT_ID and STACK_PUBLISHABLE_CLIENT_KEY are set on the API service.'
|
||||
);
|
||||
return LoadingFallback;
|
||||
}
|
||||
return (
|
||||
<Suspense fallback={LoadingFallback}>
|
||||
<StackProviderWrapper>
|
||||
<StackProviderWrapper
|
||||
projectId={config.stack.projectId}
|
||||
publishableClientKey={config.stack.publishableClientKey}
|
||||
>
|
||||
{children}
|
||||
</StackProviderWrapper>
|
||||
</Suspense>
|
||||
|
|
|
|||
|
|
@ -9,10 +9,18 @@ import { AuthContext } from './AuthProvider';
|
|||
// Create a singleton StackClientApp instance to prevent multiple initializations
|
||||
let stackClientAppInstance: StackClientApp<true, string> | null = null;
|
||||
|
||||
function getStackClientApp(): StackClientApp<true, string> {
|
||||
function getStackClientApp(
|
||||
projectId: string,
|
||||
publishableClientKey: string,
|
||||
): StackClientApp<true, string> {
|
||||
if (!stackClientAppInstance) {
|
||||
// projectId / publishableClientKey are passed explicitly (fetched from the
|
||||
// backend at runtime) instead of being read from inlined NEXT_PUBLIC_* env,
|
||||
// so the prebuilt image works without build-time configuration.
|
||||
stackClientAppInstance = new StackClientApp({
|
||||
tokenStore: "nextjs-cookie",
|
||||
projectId,
|
||||
publishableClientKey,
|
||||
urls: {
|
||||
afterSignIn: "/after-sign-in"
|
||||
}
|
||||
|
|
@ -23,6 +31,8 @@ function getStackClientApp(): StackClientApp<true, string> {
|
|||
|
||||
interface StackProviderWrapperProps {
|
||||
children: React.ReactNode;
|
||||
projectId: string;
|
||||
publishableClientKey: string;
|
||||
}
|
||||
|
||||
// Simple context provider that uses Stack's useUser directly
|
||||
|
|
@ -114,8 +124,8 @@ const translationOverrides = {
|
|||
"Sign up with {provider}": "Sign up with {provider} Business",
|
||||
};
|
||||
|
||||
export function StackProviderWrapper({ children }: StackProviderWrapperProps) {
|
||||
const stackClientApp = getStackClientApp();
|
||||
export function StackProviderWrapper({ children, projectId, publishableClientKey }: StackProviderWrapperProps) {
|
||||
const stackClientApp = getStackClientApp(projectId, publishableClientKey);
|
||||
|
||||
return (
|
||||
<StackProvider app={stackClientApp} translationOverrides={translationOverrides}>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { cookies } from 'next/headers';
|
|||
|
||||
import logger from '@/lib/logger';
|
||||
|
||||
import { getAuthProvider } from './config';
|
||||
import { getAuthProvider, getStackConfig } from './config';
|
||||
import type { LocalUser } from './types';
|
||||
|
||||
// Server-side auth utilities for SSR pages
|
||||
|
|
@ -21,10 +21,23 @@ export async function getStackServerApp(): Promise<StackServerApp<boolean, strin
|
|||
// Only import if using Stack provider
|
||||
const authProvider = await getAuthProvider();
|
||||
if (authProvider === 'stack') {
|
||||
const stackConfig = await getStackConfig();
|
||||
if (!stackConfig) {
|
||||
logger.error(
|
||||
'Auth provider is "stack" but Stack client config is unavailable from the backend ' +
|
||||
'(STACK_AUTH_PROJECT_ID / STACK_PUBLISHABLE_CLIENT_KEY).'
|
||||
);
|
||||
return null;
|
||||
}
|
||||
const stackModule = await import('@stackframe/stack');
|
||||
const { StackServerApp } = stackModule;
|
||||
// projectId / publishableClientKey come from the backend at runtime. The
|
||||
// secret server key stays a server-only runtime env var
|
||||
// (STACK_SECRET_SERVER_KEY), read by the SDK directly.
|
||||
stackServerApp = new StackServerApp({
|
||||
tokenStore: "nextjs-cookie",
|
||||
projectId: stackConfig.projectId,
|
||||
publishableClientKey: stackConfig.publishableClientKey,
|
||||
urls: {
|
||||
afterSignIn: "/after-sign-in"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,25 +105,6 @@ export async function getRedirectUrl(token: string, permissions: { id: string }[
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Cookie helpers
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export function setStackRefreshCookie(refreshToken: string) {
|
||||
const expiryDate = new Date();
|
||||
expiryDate.setFullYear(expiryDate.getFullYear() + 1);
|
||||
|
||||
const isDograhDomain = window.location.hostname.endsWith('.dograh.com');
|
||||
const cookieDomainPart = isDograhDomain ? '; domain=.dograh.com' : '';
|
||||
|
||||
document.cookie =
|
||||
`stack-refresh-${process.env.NEXT_PUBLIC_STACK_PROJECT_ID}=${refreshToken}; ` +
|
||||
`expires=${expiryDate.toUTCString()}; path=/` +
|
||||
`${cookieDomainPart}; secure; samesite=lax`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Centralised impersonation logic to avoid code duplication between pages.
|
||||
*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue