mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
feat: add mock OIDC IdP for local development
- Introduced a new mock OIDC Identity Provider in `apps/mock-oidc-idp` for OIDC federation testing. - Updated `package.json` scripts to include the new mock server. - Enhanced backend seed script to integrate with the mock IdP for trust policy setup. - Added demo pages and API routes for minting and exchanging tokens with the mock IdP. - Updated environment configurations to support the new mock IdP. This setup facilitates local testing of OIDC federation features without external dependencies.
This commit is contained in:
parent
c7f5d2f6b7
commit
92cd9965fb
@ -20,6 +20,10 @@ STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY=this-super-secret-admin-key-i
|
||||
STACK_OAUTH_MOCK_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}14
|
||||
STACK_TURNSTILE_SITEVERIFY_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}14/turnstile/siteverify
|
||||
|
||||
# Local mock OIDC IdP for OIDC federation testing (apps/mock-oidc-idp).
|
||||
# Read by the seed script to install a default trust policy on the dummy project.
|
||||
STACK_MOCK_OIDC_ISSUER_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}15
|
||||
|
||||
# Cloudflare Turnstile test keys — always-pass widgets, no real challenges
|
||||
# See https://developers.cloudflare.com/turnstile/troubleshooting/testing/
|
||||
NEXT_PUBLIC_STACK_BOT_CHALLENGE_SITE_KEY=1x00000000000000000000AA
|
||||
|
||||
@ -1906,6 +1906,29 @@ export async function seedDummyProject(options: SeedDummyProjectOptions): Promis
|
||||
.filter(([, app]) => !options.excludeAlphaApps || app.stage !== "alpha")
|
||||
.map(([key]) => [key, { enabled: true }])),
|
||||
},
|
||||
oidcFederation: {
|
||||
// Default trust policy pointing at the local mock OIDC IdP
|
||||
// (apps/mock-oidc-idp). The demo at `examples/demo/oidc-federation-demo`
|
||||
// mints tokens from this IdP and exchanges them here.
|
||||
trustPolicies: {
|
||||
"mock-idp-demo": {
|
||||
displayName: "Mock IdP (local dev)",
|
||||
enabled: true,
|
||||
issuerUrl: process.env.STACK_MOCK_OIDC_ISSUER_URL
|
||||
?? `http://localhost:${process.env.NEXT_PUBLIC_STACK_PORT_PREFIX ?? "81"}15`,
|
||||
audiences: {
|
||||
default: "stack-demo",
|
||||
},
|
||||
claimConditions: {
|
||||
stringLike: {
|
||||
sub: { demo: "workload:*" },
|
||||
},
|
||||
stringEquals: {},
|
||||
},
|
||||
tokenTtlSeconds: 900,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
overrideEnvironmentConfigOverride({
|
||||
|
||||
7
apps/mock-oidc-idp/.eslintrc.js
Normal file
7
apps/mock-oidc-idp/.eslintrc.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"../../configs/eslint/defaults.js",
|
||||
"../../configs/eslint/next.js",
|
||||
],
|
||||
"ignorePatterns": ['/*', '!/src']
|
||||
};
|
||||
23
apps/mock-oidc-idp/package.json
Normal file
23
apps/mock-oidc-idp/package.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "@stackframe/mock-oidc-idp",
|
||||
"version": "2.8.85",
|
||||
"repository": "https://github.com/stack-auth/stack-auth",
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "tsx src/index.ts",
|
||||
"dev": "tsx watch --clear-screen=false src/index.ts",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "eslint .",
|
||||
"clean": "rimraf dist && rimraf node_modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/express": "^5.0.0",
|
||||
"express": "^4.21.2",
|
||||
"jose": "^6.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsx": "^4.16.2"
|
||||
},
|
||||
"packageManager": "pnpm@10.23.0"
|
||||
}
|
||||
119
apps/mock-oidc-idp/src/index.ts
Normal file
119
apps/mock-oidc-idp/src/index.ts
Normal file
@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Mock OIDC Identity Provider for local development of the Stack Auth
|
||||
* OIDC-federation feature. It mimics the discovery + JWKS + token-minting
|
||||
* surface that Vercel / GitHub Actions / GCP / any OIDC-compliant IdP expose
|
||||
* at runtime, so the backend's `/api/v1/auth/oidc-federation/exchange`
|
||||
* endpoint has something to validate against without network egress.
|
||||
*
|
||||
* Endpoints:
|
||||
* GET /.well-known/openid-configuration — OIDC discovery doc
|
||||
* GET /jwks — JSON Web Key Set (RSA public key)
|
||||
* POST /mint — non-standard. Mints an ID-token
|
||||
* with caller-supplied `sub`, `aud`,
|
||||
* and extra claims. Used by the
|
||||
* demo app to simulate a workload
|
||||
* token without the caller having
|
||||
* to sign their own JWT.
|
||||
*
|
||||
* This server does NOT implement the OAuth 2.0 authorization-code flow. It
|
||||
* exists solely to serve as a trusted OIDC issuer for federation testing.
|
||||
*/
|
||||
|
||||
import express from "express";
|
||||
import { SignJWT, exportJWK, generateKeyPair } from "jose";
|
||||
|
||||
const stackPortPrefix = process.env.NEXT_PUBLIC_STACK_PORT_PREFIX ?? "81";
|
||||
const defaultPort = Number(`${stackPortPrefix}15`);
|
||||
const port = Number(process.env.PORT ?? defaultPort);
|
||||
const issuer = process.env.STACK_MOCK_OIDC_ISSUER_URL ?? `http://localhost:${port}`;
|
||||
|
||||
async function main() {
|
||||
const { publicKey, privateKey } = await generateKeyPair("RS256", { extractable: true });
|
||||
const publicJwk = await exportJWK(publicKey);
|
||||
publicJwk.kid = "mock-oidc-idp-key-1";
|
||||
publicJwk.alg = "RS256";
|
||||
publicJwk.use = "sig";
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
app.get("/.well-known/openid-configuration", (_req, res) => {
|
||||
res.json({
|
||||
issuer,
|
||||
jwks_uri: `${issuer}/jwks`,
|
||||
id_token_signing_alg_values_supported: ["RS256"],
|
||||
// The remaining fields are not used by the backend but make this doc
|
||||
// parseable by generic OIDC clients that probe a few optional keys.
|
||||
response_types_supported: ["id_token"],
|
||||
subject_types_supported: ["public"],
|
||||
token_endpoint_auth_methods_supported: ["none"],
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/jwks", (_req, res) => {
|
||||
res.json({ keys: [publicJwk] });
|
||||
});
|
||||
|
||||
// Non-standard mint endpoint. Real IdPs don't expose anything like this —
|
||||
// they mint tokens at the end of a controlled auth flow. We expose it so
|
||||
// the demo app can request a workload-style token on demand.
|
||||
app.post("/mint", async (req, res) => {
|
||||
const body = (req.body ?? {}) as {
|
||||
sub?: unknown,
|
||||
aud?: unknown,
|
||||
extraClaims?: unknown,
|
||||
ttlSeconds?: unknown,
|
||||
};
|
||||
const sub = typeof body.sub === "string" && body.sub.length > 0 ? body.sub : "workload:demo";
|
||||
const aud = typeof body.aud === "string" && body.aud.length > 0 ? body.aud : `${issuer}/default-audience`;
|
||||
const ttlSeconds = typeof body.ttlSeconds === "number" && body.ttlSeconds > 0 && body.ttlSeconds <= 3600 ? body.ttlSeconds : 300;
|
||||
const extraClaims = typeof body.extraClaims === "object" && body.extraClaims !== null && !Array.isArray(body.extraClaims)
|
||||
? (body.extraClaims as Record<string, unknown>)
|
||||
: {};
|
||||
|
||||
const jwt = await new SignJWT(extraClaims)
|
||||
.setProtectedHeader({ alg: "RS256", kid: publicJwk.kid, typ: "JWT" })
|
||||
.setIssuer(issuer)
|
||||
.setSubject(sub)
|
||||
.setAudience(aud)
|
||||
.setIssuedAt()
|
||||
.setExpirationTime(Math.floor(Date.now() / 1000) + ttlSeconds)
|
||||
.sign(privateKey);
|
||||
|
||||
res.json({
|
||||
id_token: jwt,
|
||||
issuer,
|
||||
sub,
|
||||
aud,
|
||||
expires_in: ttlSeconds,
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/", (_req, res) => {
|
||||
res.type("text/plain").send(
|
||||
[
|
||||
"Mock OIDC IdP (Stack Auth OIDC federation local dev)",
|
||||
`issuer: ${issuer}`,
|
||||
"",
|
||||
"GET /.well-known/openid-configuration",
|
||||
"GET /jwks",
|
||||
"POST /mint { sub, aud, extraClaims, ttlSeconds } -> { id_token, ... }",
|
||||
].join("\n"),
|
||||
);
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Mock OIDC IdP listening on ${issuer}`);
|
||||
});
|
||||
}
|
||||
|
||||
void main().then(
|
||||
() => {
|
||||
// started OK — listener logs its own ready message
|
||||
},
|
||||
(err: unknown) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Mock OIDC IdP failed to start:", err);
|
||||
process.exit(1);
|
||||
},
|
||||
);
|
||||
19
apps/mock-oidc-idp/tsconfig.json
Normal file
19
apps/mock-oidc-idp/tsconfig.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"noErrorTruncation": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@ -1,3 +1,7 @@
|
||||
NEXT_PUBLIC_STACK_API_URL=# enter your stack endpoint here, e.g. http://localhost:8102
|
||||
NEXT_PUBLIC_STACK_PROJECT_ID=# enter your stack project id here
|
||||
NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=# enter your stack publishable client key here
|
||||
|
||||
# OIDC federation demo — /oidc-federation-demo
|
||||
# URL of the local mock OIDC IdP (apps/mock-oidc-idp). Only needed for that demo route.
|
||||
STACK_MOCK_OIDC_ISSUER_URL=# e.g. http://localhost:8115
|
||||
|
||||
@ -5,3 +5,7 @@ NEXT_PUBLIC_STACK_PROJECT_ID=internal
|
||||
NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=this-publishable-client-key-is-for-local-development-only
|
||||
STACK_SECRET_SERVER_KEY=this-secret-server-key-is-for-local-development-only
|
||||
NEXT_PUBLIC_STACK_HOSTED_HANDLER_URL_TEMPLATE=http://{projectId}.localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}09/{hostedPath}
|
||||
|
||||
# OIDC federation demo — /oidc-federation-demo
|
||||
# URL of the local mock OIDC IdP (apps/mock-oidc-idp).
|
||||
STACK_MOCK_OIDC_ISSUER_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}15
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
const BACKEND_URL = process.env.NEXT_PUBLIC_STACK_API_URL
|
||||
?? `http://localhost:${process.env.NEXT_PUBLIC_STACK_PORT_PREFIX ?? "81"}02`;
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const body = await request.json() as { projectId?: string, subjectToken?: string };
|
||||
if (!body.projectId || !body.subjectToken) {
|
||||
return NextResponse.json({ ok: false, status: 400, error: "projectId and subjectToken are required" }, { status: 400 });
|
||||
}
|
||||
|
||||
const res = await fetch(`${BACKEND_URL}/api/v1/auth/oidc-federation/exchange`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-stack-project-id": body.projectId,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
|
||||
subject_token: body.subjectToken,
|
||||
subject_token_type: "urn:ietf:params:oauth:token-type:jwt",
|
||||
}),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
return NextResponse.json({ ok: false, status: res.status, error: text }, { status: 200 });
|
||||
}
|
||||
const data = await res.json() as { access_token: string, expires_in: number, token_type: string };
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
access_token: data.access_token,
|
||||
expires_in: data.expires_in,
|
||||
token_type: data.token_type,
|
||||
});
|
||||
}
|
||||
23
examples/demo/src/app/oidc-federation-demo/api/mint/route.ts
Normal file
23
examples/demo/src/app/oidc-federation-demo/api/mint/route.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
const MOCK_IDP_URL = process.env.STACK_MOCK_OIDC_ISSUER_URL
|
||||
?? `http://localhost:${process.env.NEXT_PUBLIC_STACK_PORT_PREFIX ?? "81"}15`;
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const body = await request.json() as { sub?: string, aud?: string };
|
||||
const res = await fetch(`${MOCK_IDP_URL}/mint`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
sub: body.sub,
|
||||
aud: body.aud,
|
||||
extraClaims: { environment: "demo" },
|
||||
ttlSeconds: 300,
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
return NextResponse.json({ error: data?.error ?? `mock IdP returned ${res.status}` }, { status: 502 });
|
||||
}
|
||||
return NextResponse.json(data);
|
||||
}
|
||||
163
examples/demo/src/app/oidc-federation-demo/page.tsx
Normal file
163
examples/demo/src/app/oidc-federation-demo/page.tsx
Normal file
@ -0,0 +1,163 @@
|
||||
"use client";
|
||||
|
||||
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import { Button, Card, Input, Typography } from "@stackframe/stack-ui";
|
||||
import { useState } from "react";
|
||||
|
||||
type MintResult = {
|
||||
id_token: string,
|
||||
issuer: string,
|
||||
sub: string,
|
||||
aud: string,
|
||||
expires_in: number,
|
||||
};
|
||||
|
||||
type ExchangeOk = {
|
||||
ok: true,
|
||||
access_token: string,
|
||||
expires_in: number,
|
||||
token_type: string,
|
||||
};
|
||||
|
||||
type ExchangeErr = {
|
||||
ok: false,
|
||||
status: number,
|
||||
error: string,
|
||||
};
|
||||
|
||||
type ExchangeResult = ExchangeOk | ExchangeErr;
|
||||
|
||||
const DEFAULT_PROJECT_ID = "6fbbf22e-f4b2-4c6e-95a1-beab6fa41063";
|
||||
|
||||
export default function OidcFederationDemoPage() {
|
||||
const [projectId, setProjectId] = useState(DEFAULT_PROJECT_ID);
|
||||
const [sub, setSub] = useState("workload:demo-1");
|
||||
const [aud, setAud] = useState("stack-demo");
|
||||
const [minting, setMinting] = useState(false);
|
||||
const [exchanging, setExchanging] = useState(false);
|
||||
const [mintResult, setMintResult] = useState<MintResult | null>(null);
|
||||
const [exchangeResult, setExchangeResult] = useState<ExchangeResult | null>(null);
|
||||
|
||||
const mint = async () => {
|
||||
setMinting(true);
|
||||
setMintResult(null);
|
||||
setExchangeResult(null);
|
||||
try {
|
||||
const res = await fetch("/oidc-federation-demo/api/mint", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ sub, aud }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error ?? "mint failed");
|
||||
setMintResult(data);
|
||||
} finally {
|
||||
setMinting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const exchange = async () => {
|
||||
if (!mintResult) return;
|
||||
setExchanging(true);
|
||||
setExchangeResult(null);
|
||||
try {
|
||||
const res = await fetch("/oidc-federation-demo/api/exchange", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ projectId, subjectToken: mintResult.id_token }),
|
||||
});
|
||||
const data = await res.json();
|
||||
setExchangeResult(data);
|
||||
} finally {
|
||||
setExchanging(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="stack-scope min-h-screen flex items-start justify-center p-6 w-full">
|
||||
<div className="w-full max-w-3xl flex flex-col gap-6">
|
||||
<div>
|
||||
<Typography type="h2" className="mb-1">OIDC Federation demo</Typography>
|
||||
<Typography variant="secondary">
|
||||
Step 1 mints a mock workload JWT from <code>apps/mock-oidc-idp</code>. Step 2 exchanges
|
||||
it at the backend for a short-lived Stack server access token via RFC 8693. The
|
||||
dummy project is pre-seeded with a trust policy accepting <code>workload:*</code> subs
|
||||
from this IdP with audience <code>stack-demo</code>.
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<Card className="p-5 flex flex-col gap-4">
|
||||
<Typography type="h4">1. Mint mock OIDC token</Typography>
|
||||
<LabelledInput label="Subject (sub)" value={sub} onChange={setSub} placeholder="workload:demo-1" />
|
||||
<LabelledInput label="Audience (aud)" value={aud} onChange={setAud} placeholder="stack-demo" />
|
||||
<Button
|
||||
onClick={() => runAsynchronouslyWithAlert(mint())}
|
||||
disabled={minting}
|
||||
>
|
||||
{minting ? "Minting…" : "Mint token"}
|
||||
</Button>
|
||||
{mintResult && (
|
||||
<KeyValue label="id_token">
|
||||
<pre className="text-xs overflow-x-auto p-2 bg-muted rounded whitespace-pre-wrap break-all">
|
||||
{mintResult.id_token}
|
||||
</pre>
|
||||
<Typography variant="secondary" className="text-xs">
|
||||
issuer <code>{mintResult.issuer}</code> · aud <code>{mintResult.aud}</code> · ttl {mintResult.expires_in}s
|
||||
</Typography>
|
||||
</KeyValue>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
<Card className="p-5 flex flex-col gap-4">
|
||||
<Typography type="h4">2. Exchange for Stack server access token</Typography>
|
||||
<LabelledInput label="Project ID" value={projectId} onChange={setProjectId} placeholder="project uuid" />
|
||||
<Button
|
||||
onClick={() => runAsynchronouslyWithAlert(exchange())}
|
||||
disabled={exchanging || !mintResult}
|
||||
>
|
||||
{exchanging ? "Exchanging…" : mintResult ? "Exchange token" : "Mint a token first"}
|
||||
</Button>
|
||||
{exchangeResult && (exchangeResult.ok ? (
|
||||
<div className="p-3 rounded bg-green-50 dark:bg-green-900/20 flex flex-col gap-2">
|
||||
<Typography className="text-green-700 dark:text-green-400 font-medium">
|
||||
Exchange OK — token expires in {exchangeResult.expires_in}s
|
||||
</Typography>
|
||||
<pre className="text-xs overflow-x-auto p-2 bg-green-100 dark:bg-green-900/40 rounded whitespace-pre-wrap break-all">
|
||||
{exchangeResult.access_token}
|
||||
</pre>
|
||||
<Typography variant="secondary" className="text-xs">
|
||||
Use this as <code>x-stack-server-access-token</code> on server-scope API calls — no
|
||||
<code> STACK_SECRET_SERVER_KEY</code> needed.
|
||||
</Typography>
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-3 rounded bg-red-50 dark:bg-red-900/20 flex flex-col gap-1">
|
||||
<Typography className="text-red-700 dark:text-red-400 font-medium">
|
||||
Exchange failed ({exchangeResult.status})
|
||||
</Typography>
|
||||
<Typography variant="secondary" className="text-xs">{exchangeResult.error}</Typography>
|
||||
</div>
|
||||
))}
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LabelledInput({ label, value, onChange, placeholder }: { label: string, value: string, onChange: (v: string) => void, placeholder?: string }) {
|
||||
return (
|
||||
<div>
|
||||
<Typography variant="secondary" className="text-xs mb-1 block">{label}</Typography>
|
||||
<Input value={value} onChange={(e) => onChange(e.target.value)} placeholder={placeholder} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function KeyValue({ label, children }: { label: string, children: React.ReactNode }) {
|
||||
return (
|
||||
<div>
|
||||
<Typography variant="secondary" className="text-xs mb-1 block">{label}</Typography>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -54,15 +54,16 @@
|
||||
"dev:tui": "pnpm pre && (trap 'kill 0' EXIT; pnpm run generate-sdks:watch & pnpm run generate-openapi-docs:watch & turbo run dev --ui tui --concurrency 99999 --filter=./apps/* --filter=@stackframe/stack-docs --filter=./packages/* --filter=./examples/demo)",
|
||||
"dev:inspect": "pnpm pre && STACK_BACKEND_DEV_EXTRA_ARGS=\"--inspect\" pnpm run dev",
|
||||
"dev:profile": "pnpm pre && STACK_BACKEND_DEV_EXTRA_ARGS=\"--experimental-cpu-prof\" pnpm run dev",
|
||||
"dev:basic": "pnpm pre && concurrently -k \"pnpm run generate-sdks:watch\" \"turbo run dev --concurrency 99999 --filter=@stackframe/backend --filter=@stackframe/dashboard --filter=@stackframe/mock-oauth-server\"",
|
||||
"dev:basic": "pnpm pre && concurrently -k \"pnpm run generate-sdks:watch\" \"turbo run dev --concurrency 99999 --filter=@stackframe/backend --filter=@stackframe/dashboard --filter=@stackframe/mock-oauth-server --filter=@stackframe/mock-oidc-idp\"",
|
||||
"dev:docs": "pnpm pre && concurrently -k \"pnpm run generate-openapi-docs:watch\" \"turbo run dev --concurrency 99999 --filter=@stackframe/stack-docs\"",
|
||||
"dev:named": "pnpm pre && concurrently -k \"pnpm run dev\" \"node -e \\\"process.title='node (stack-named-dev-server)'; process.stdin.resume();\\\"\"",
|
||||
"kill-dev:named": "(pgrep -f 'stack-named-dev-server' | xargs -r -n1 pkill -P); echo 'Killed named dev server (if found). Sleeping to give some time for it to shut down...' && sleep 10",
|
||||
"kms": "PREFIX=${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}; for p in 00 01 02 03 04 06 14; do pids=$(lsof -i :$PREFIX$p 2>/dev/null | grep LISTEN | awk '$1 != \"OrbStack\" {print $2}' | sort -u); [ -n \"$pids\" ] && echo $pids | xargs kill -9 2>/dev/null; done; echo Done.",
|
||||
"kms": "PREFIX=${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}; for p in 00 01 02 03 04 06 14 15; do pids=$(lsof -i :$PREFIX$p 2>/dev/null | grep LISTEN | awk '$1 != \"OrbStack\" {print $2}' | sort -u); [ -n \"$pids\" ] && echo $pids | xargs kill -9 2>/dev/null; done; echo Done.",
|
||||
"start": "pnpm pre && turbo run start --concurrency 99999",
|
||||
"start:backend": "pnpm pre && turbo run start --concurrency 99999 --filter=@stackframe/backend",
|
||||
"start:dashboard": "pnpm pre && turbo run start --concurrency 99999 --filter=@stackframe/dashboard",
|
||||
"start:mock-oauth-server": "pnpm pre && turbo run start --concurrency 99999 --filter=@stackframe/mock-oauth-server",
|
||||
"start:mock-oidc-idp": "pnpm pre && turbo run start --concurrency 99999 --filter=@stackframe/mock-oidc-idp",
|
||||
"lint": "pnpm pre && turbo run lint --continue -- --max-warnings=0",
|
||||
"release": "pnpm pre && release",
|
||||
"dotenv": "dotenv",
|
||||
|
||||
@ -862,6 +862,22 @@ importers:
|
||||
specifier: ^4.16.2
|
||||
version: 4.16.2
|
||||
|
||||
apps/mock-oidc-idp:
|
||||
dependencies:
|
||||
'@types/express':
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0
|
||||
express:
|
||||
specifier: ^4.21.2
|
||||
version: 4.21.2
|
||||
jose:
|
||||
specifier: ^6.1.3
|
||||
version: 6.1.3
|
||||
devDependencies:
|
||||
tsx:
|
||||
specifier: ^4.16.2
|
||||
version: 4.21.0
|
||||
|
||||
docs:
|
||||
dependencies:
|
||||
2027-track:
|
||||
@ -40842,7 +40858,7 @@ snapshots:
|
||||
tsx@4.21.0:
|
||||
dependencies:
|
||||
esbuild: 0.27.1
|
||||
get-tsconfig: 4.8.1
|
||||
get-tsconfig: 4.13.6
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user