mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
Merge dev into api-keys-revocation-endpoint
This commit is contained in:
commit
9b034fb6e2
@ -9,7 +9,8 @@
|
||||
"ghcr.io/devcontainers-contrib/features/pnpm:2": {
|
||||
"version": "9"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/git:1": {}
|
||||
"ghcr.io/devcontainers/features/git:1": {},
|
||||
"github-cli": "latest"
|
||||
},
|
||||
"hostRequirements": {
|
||||
"cpus": 2,
|
||||
@ -31,8 +32,9 @@
|
||||
8180, 8181, 8182, 8183, 8184, 8185, 8186, 8187, 8188, 8189,
|
||||
8190, 8191, 8192, 8193, 8194, 8195, 8196, 8197, 8198, 8199
|
||||
],
|
||||
"postCreateCommand": "pnpm install && pnpm build:packages && pnpm codegen && pnpm run start-deps && pnpm run stop-deps",
|
||||
"postStartCommand": "pnpm install && clear && echo 'To start the development server with dependencies, run: pnpm restart-deps && pnpm dev' && echo 'To run the tests, run: pnpm test [<path-filter>]'",
|
||||
"postCreateCommand": "chmod +x .devcontainer/set-env.sh && pnpm install && pnpm build:packages && pnpm codegen && pnpm run start-deps && pnpm run stop-deps",
|
||||
"postStartCommand": "pnpm install && clear",
|
||||
"postAttachCommand": ". .devcontainer/set-env.sh",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
|
||||
10
.devcontainer/set-env.sh
Executable file
10
.devcontainer/set-env.sh
Executable file
@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -n "$CODESPACE_NAME" ]; then
|
||||
export NEXT_PUBLIC_STACK_API_URL="https://${CODESPACE_NAME}-8102.app.github.dev"
|
||||
export STACK_MOCK_OAUTH_REDIRECT_URIS="https://${CODESPACE_NAME}-8102.app.github.dev/api/v1/auth/oauth/callback/{id}"
|
||||
export NEXT_PUBLIC_STACK_DASHBOARD_URL="https://${CODESPACE_NAME}-8101.app.github.dev"
|
||||
gh codespace ports visibility 8102:public -c $CODESPACE_NAME && gh codespace ports visibility 8114:public -c $CODESPACE_NAME
|
||||
fi
|
||||
|
||||
echo 'To start the development server with dependencies, run: pnpm restart-deps && pnpm dev'
|
||||
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -11,6 +11,7 @@
|
||||
"backlinks",
|
||||
"Cancelation",
|
||||
"Cdfc",
|
||||
"checksummable",
|
||||
"chinthakagodawita",
|
||||
"cjsx",
|
||||
"clsx",
|
||||
|
||||
@ -103,6 +103,7 @@ export async function generateAccessToken(options: {
|
||||
sub: options.userId,
|
||||
branchId: options.tenancy.branchId,
|
||||
refreshTokenId: options.refreshTokenId,
|
||||
role: 'authenticated',
|
||||
},
|
||||
expirationTime: getEnvVariable("STACK_ACCESS_TOKEN_EXPIRATION_TIME", "10min"),
|
||||
});
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
"lucide-react": "^0.378.0",
|
||||
"next": "15.2.3",
|
||||
"next-themes": "^0.2.1",
|
||||
"posthog-js": "^1.149.1",
|
||||
"posthog-js": "^1.234.9",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-globe.gl": "^2.28.2",
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { Confetti } from "@/components/confetti";
|
||||
import { Card, CardContent, CardFooter, CardHeader, InlineCode, Typography } from "@stackframe/stack-ui";
|
||||
import Actions from "./actions";
|
||||
import PostHog from "./posthog";
|
||||
|
||||
export const metadata = {
|
||||
title: "Setup complete!",
|
||||
@ -9,6 +10,7 @@ export const metadata = {
|
||||
export default function WizardCongratsPage() {
|
||||
return (
|
||||
<>
|
||||
<PostHog />
|
||||
<Confetti />
|
||||
<style>
|
||||
{`
|
||||
|
||||
32
apps/dashboard/src/app/(main)/wizard-congrats/posthog.tsx
Normal file
32
apps/dashboard/src/app/(main)/wizard-congrats/posthog.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "@/components/router";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { usePostHog } from "posthog-js/react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function PostHog() {
|
||||
const posthog = usePostHog();
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
useEffect(() => {
|
||||
const distinctId = searchParams.get("stack-init-id");
|
||||
if (distinctId) {
|
||||
posthog.capture('$merge_dangerously',
|
||||
{
|
||||
alias: distinctId,
|
||||
});
|
||||
const newSearchParams = new URLSearchParams();
|
||||
searchParams.forEach((value, key) => {
|
||||
if (key !== "stack-init-id") {
|
||||
newSearchParams.append(key, value);
|
||||
}
|
||||
});
|
||||
const newUrl = window.location.pathname +
|
||||
(newSearchParams.toString() ? `?${newSearchParams.toString()}` : '');
|
||||
router.replace(newUrl);
|
||||
}
|
||||
}, [posthog, searchParams, router]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@ -191,6 +191,7 @@ export namespace Auth {
|
||||
"refreshTokenId": expect.any(String),
|
||||
"aud": expect.any(String),
|
||||
"sub": expect.any(String),
|
||||
"role": "authenticated",
|
||||
"branchId": "main",
|
||||
});
|
||||
}
|
||||
|
||||
@ -18,9 +18,12 @@ const providerIds = [
|
||||
const clients = providerIds.map((id) => ({
|
||||
client_id: id,
|
||||
client_secret: 'MOCK-SERVER-SECRET',
|
||||
redirect_uris: [8102, 32102].map(port =>
|
||||
`http://localhost:${port}/api/v1/auth/oauth/callback/${id}`
|
||||
),
|
||||
redirect_uris: [
|
||||
...([8102, 32102].map(port =>
|
||||
`http://localhost:${port}/api/v1/auth/oauth/callback/${id}`
|
||||
)),
|
||||
...(process.env.STACK_MOCK_OAUTH_REDIRECT_URIS ? [process.env.STACK_MOCK_OAUTH_REDIRECT_URIS.replace("{id}", id)] : [])
|
||||
]
|
||||
}));
|
||||
|
||||
const configuration = {
|
||||
|
||||
@ -60,6 +60,10 @@ Please refer to the webhook endpoint API reference for more details on the avail
|
||||
- [team.created](/rest-api/webhooks/teams/team-created)
|
||||
- [team.updated](/rest-api/webhooks/teams/team-updated)
|
||||
- [team.deleted](/rest-api/webhooks/teams/team-deleted)
|
||||
- [team_membership.created](/rest-api/webhooks/teams/team-membership-created)
|
||||
- [team_membership.deleted](/rest-api/webhooks/teams/team-membership-deleted)
|
||||
- [team_permission.created](/rest-api/webhooks/teams/team-permission-created)
|
||||
- [team_permission.deleted](/rest-api/webhooks/teams/team-permission-deleted)
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@ -40,7 +40,8 @@
|
||||
"@stackframe/stack-shared": "workspace:*",
|
||||
"commander": "^13.1.0",
|
||||
"inquirer": "^9.2.19",
|
||||
"open": "^10.1.0"
|
||||
"open": "^10.1.0",
|
||||
"posthog-node": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/inquirer": "^9.0.7",
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
import * as child_process from "child_process";
|
||||
import { Command } from "commander";
|
||||
import * as crypto from 'crypto';
|
||||
import * as fs from "fs";
|
||||
import inquirer from "inquirer";
|
||||
import open from "open";
|
||||
import * as os from 'os';
|
||||
import * as path from "path";
|
||||
import { PostHog } from 'posthog-node';
|
||||
import packageJson from '../package.json';
|
||||
|
||||
const jsLikeFileExtensions: string[] = [
|
||||
@ -113,6 +116,26 @@ const nextSteps: string[] = [
|
||||
`Create an account and Stack Auth API key for your project on https://app.stack-auth.com`,
|
||||
];
|
||||
|
||||
|
||||
const STACK_AUTH_PUBLIC_HOG_KEY = "phc_vIUFi0HzHo7oV26OsaZbUASqxvs8qOmap1UBYAutU4k";
|
||||
const EVENT_PREFIX = "stack-init-";
|
||||
const ph_client = new PostHog(STACK_AUTH_PUBLIC_HOG_KEY, {
|
||||
host: "https://eu.i.posthog.com",
|
||||
flushAt: 1,
|
||||
flushInterval: 0,
|
||||
});
|
||||
const distinctId = crypto.randomUUID();
|
||||
|
||||
|
||||
async function capture(event: string, properties: Record<string, any>) {
|
||||
ph_client.capture({
|
||||
event: `${EVENT_PREFIX}${event}`,
|
||||
distinctId,
|
||||
properties,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function main(): Promise<void> {
|
||||
// Welcome message
|
||||
console.log();
|
||||
@ -132,6 +155,19 @@ async function main(): Promise<void> {
|
||||
`);
|
||||
console.log();
|
||||
|
||||
await capture("start", {
|
||||
version: packageJson.version,
|
||||
isDryRun,
|
||||
isNeon,
|
||||
typeFromArgs,
|
||||
packageManagerFromArgs,
|
||||
isClient,
|
||||
isServer,
|
||||
noBrowser,
|
||||
platform: os.platform(),
|
||||
arch: os.arch(),
|
||||
nodeVersion: process.version,
|
||||
});
|
||||
|
||||
// Wait just briefly so we can use `Steps` in here (it's defined only after the call to `main()`)
|
||||
await new Promise<void>((resolve) => resolve());
|
||||
@ -143,8 +179,13 @@ async function main(): Promise<void> {
|
||||
|
||||
|
||||
// Steps
|
||||
const { packageJson } = await Steps.getProject();
|
||||
const type = await Steps.getProjectType({ packageJson });
|
||||
const { packageJson: projectPackageJson } = await Steps.getProject();
|
||||
const type = await Steps.getProjectType({ packageJson: projectPackageJson });
|
||||
|
||||
await capture("project-type-selected", {
|
||||
type,
|
||||
wasSpecifiedInArgs: !!typeFromArgs,
|
||||
});
|
||||
|
||||
await Steps.addStackPackage(type);
|
||||
if (isNeon) packagesToInstall.push('@neondatabase/serverless');
|
||||
@ -152,7 +193,7 @@ async function main(): Promise<void> {
|
||||
await Steps.writeEnvVars(type);
|
||||
|
||||
if (type === "next") {
|
||||
const projectInfo = await Steps.getNextProjectInfo({ packageJson });
|
||||
const projectInfo = await Steps.getNextProjectInfo({ packageJson: projectPackageJson });
|
||||
await Steps.updateNextLayoutFile(projectInfo);
|
||||
await Steps.writeStackAppFile(projectInfo, "server");
|
||||
await Steps.writeNextHandlerFile(projectInfo);
|
||||
@ -181,6 +222,12 @@ async function main(): Promise<void> {
|
||||
}
|
||||
|
||||
const { packageManager } = await Steps.getPackageManager();
|
||||
|
||||
await capture(`package-manager-selected`, {
|
||||
packageManager,
|
||||
wasSpecifiedInArgs: !!packageManagerFromArgs,
|
||||
});
|
||||
|
||||
await Steps.ensureReady(type);
|
||||
|
||||
|
||||
@ -193,6 +240,10 @@ async function main(): Promise<void> {
|
||||
cwd: projectPath,
|
||||
});
|
||||
|
||||
await capture(`dependencies-installed`, {
|
||||
packageManager,
|
||||
packages: packagesToInstall,
|
||||
});
|
||||
|
||||
// Write files
|
||||
console.log();
|
||||
@ -220,6 +271,18 @@ async function main(): Promise<void> {
|
||||
}
|
||||
console.log();
|
||||
|
||||
await capture("complete", {
|
||||
success: true,
|
||||
type,
|
||||
packageManager,
|
||||
isNeon,
|
||||
isClient,
|
||||
isServer,
|
||||
noBrowser,
|
||||
filesCreated,
|
||||
filesModified,
|
||||
commandsExecuted,
|
||||
});
|
||||
|
||||
// Success!
|
||||
console.log(`
|
||||
@ -230,8 +293,8 @@ ${colorize.green`Successfully installed Stack! 🚀🚀🚀`}
|
||||
${colorize.bold`Next steps:`}
|
||||
|
||||
1. ${noBrowser ?
|
||||
`Create a project at https://app.stack-auth.com and get your API keys` :
|
||||
`Complete the setup in your browser to get your API keys`}
|
||||
`Create a project at https://app.stack-auth.com and get your API keys` :
|
||||
`Complete the setup in your browser to get your API keys`}
|
||||
2. Add the API keys to your .env.local file
|
||||
3. Import the Stack components in your app
|
||||
4. Add authentication to your app
|
||||
@ -239,12 +302,20 @@ ${colorize.bold`Next steps:`}
|
||||
For more information, please visit https://docs.stack-auth.com/getting-started/setup
|
||||
`.trim());
|
||||
if (!process.env.STACK_DISABLE_INTERACTIVE && !noBrowser) {
|
||||
await open("https://app.stack-auth.com/wizard-congrats");
|
||||
await open(`https://app.stack-auth.com/wizard-congrats?stack-init-id=${encodeURIComponent(distinctId)}`);
|
||||
}
|
||||
await ph_client.shutdown();
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((err) => {
|
||||
.catch(async (err) => {
|
||||
try {
|
||||
await capture("error", {
|
||||
error: err.message,
|
||||
errorType: err instanceof UserError ? "UserError" : "SystemError",
|
||||
stack: err.stack,
|
||||
});
|
||||
} catch (e) { }
|
||||
if (!(err instanceof UserError)) {
|
||||
console.error(err);
|
||||
}
|
||||
@ -267,6 +338,7 @@ main()
|
||||
console.error(`Error message: ${err.message}`);
|
||||
}
|
||||
console.error();
|
||||
await ph_client.shutdown();
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
|
||||
@ -11,7 +11,8 @@ const API_KEY_LENGTHS = {
|
||||
SECRET_PART: 45,
|
||||
ID_PART: 32,
|
||||
TYPE_PART: 4,
|
||||
SCANNER_AND_MARKER: 10,
|
||||
SCANNER: 1,
|
||||
MARKER: 9,
|
||||
CHECKSUM: 8,
|
||||
} as const;
|
||||
|
||||
@ -59,12 +60,13 @@ function createApiKeyParts(options: Pick<ProjectApiKey, "id" | "isPublic" | "isC
|
||||
|
||||
function parseApiKeyParts(secret: string) {
|
||||
const regex = new RegExp(
|
||||
`^([^_]+)_` + // prefix
|
||||
`(.{${API_KEY_LENGTHS.SECRET_PART}})` + // secretPart
|
||||
`(.{${API_KEY_LENGTHS.ID_PART}})` + // idPart
|
||||
`(.{${API_KEY_LENGTHS.TYPE_PART}})` + // type
|
||||
`(.{${API_KEY_LENGTHS.SCANNER_AND_MARKER}})` + // scannerAndMarker
|
||||
`(.{${API_KEY_LENGTHS.CHECKSUM}})$` // checksum
|
||||
`^([a-zA-Z0-9_]+)_` + // prefix
|
||||
`([a-zA-Z0-9_]{${API_KEY_LENGTHS.SECRET_PART}})` + // secretPart
|
||||
`([a-zA-Z0-9_]{${API_KEY_LENGTHS.ID_PART}})` + // idPart
|
||||
`([a-zA-Z0-9_]{${API_KEY_LENGTHS.TYPE_PART}})` + // type
|
||||
`([a-zA-Z0-9_]{${API_KEY_LENGTHS.SCANNER}})` + // scanner
|
||||
`(${STACK_AUTH_MARKER})` + // marker
|
||||
`([a-zA-Z0-9_]{${API_KEY_LENGTHS.CHECKSUM}})$` // checksum
|
||||
);
|
||||
|
||||
const match = secret.match(regex);
|
||||
@ -72,13 +74,12 @@ function parseApiKeyParts(secret: string) {
|
||||
throw new StackAssertionError("Invalid API key format");
|
||||
}
|
||||
|
||||
const [, prefix, secretPart, idPart, type, scannerAndMarker, checksum] = match;
|
||||
const [, prefix, secretPart, idPart, type, scannerFlag, marker, checksum] = match;
|
||||
|
||||
const scannerFlag = scannerAndMarker.replace(STACK_AUTH_MARKER, "");
|
||||
const isCloudVersion = parseInt(scannerFlag, 32) % 2 === 0;
|
||||
const isPublic = (parseInt(scannerFlag, 32) & 2) !== 0;
|
||||
|
||||
const checksummablePart = `${prefix}_${secretPart}${idPart}${type}${scannerAndMarker}`;
|
||||
const checksummablePart = `${prefix}_${secretPart}${idPart}${type}${scannerFlag}${marker}`;
|
||||
const restored_id = idPart.replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, "$1-$2-$3-$4-$5");
|
||||
|
||||
if (!["user", "team"].includes(type)) {
|
||||
|
||||
@ -323,8 +323,8 @@ importers:
|
||||
specifier: ^0.2.1
|
||||
version: 0.2.1(next@15.2.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
posthog-js:
|
||||
specifier: ^1.149.1
|
||||
version: 1.149.2
|
||||
specifier: ^1.234.9
|
||||
version: 1.234.9
|
||||
react:
|
||||
specifier: 19.0.0
|
||||
version: 19.0.0
|
||||
@ -844,6 +844,9 @@ importers:
|
||||
open:
|
||||
specifier: ^10.1.0
|
||||
version: 10.1.0
|
||||
posthog-node:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0
|
||||
devDependencies:
|
||||
'@types/inquirer':
|
||||
specifier: ^9.0.7
|
||||
@ -7197,6 +7200,9 @@ packages:
|
||||
core-js-compat@3.40.0:
|
||||
resolution: {integrity: sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==}
|
||||
|
||||
core-js@3.41.0:
|
||||
resolution: {integrity: sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA==}
|
||||
|
||||
core-util-is@1.0.3:
|
||||
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
||||
|
||||
@ -10123,8 +10129,16 @@ packages:
|
||||
resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
posthog-js@1.149.2:
|
||||
resolution: {integrity: sha512-4tNtVJkq3wZ5CvfOEp3Jtl/r3ogZb5To+bdu7JoO5QjkpTY9TV1pfo/Ag4keODpAzRDahC8OaCoIr4mY3dSK4g==}
|
||||
posthog-js@1.234.9:
|
||||
resolution: {integrity: sha512-Qxpg9YOlLa59lbkYcONZ9efmB6KTgkePnkhCTSnKIyUd826cs4J6VDTKHu+V/lugRtWLWB9R7CckvFE8QPaTbg==}
|
||||
peerDependencies:
|
||||
'@rrweb/types': 2.0.0-alpha.17
|
||||
rrweb-snapshot: 2.0.0-alpha.17
|
||||
peerDependenciesMeta:
|
||||
'@rrweb/types':
|
||||
optional: true
|
||||
rrweb-snapshot:
|
||||
optional: true
|
||||
|
||||
posthog-node@4.1.0:
|
||||
resolution: {integrity: sha512-Fd+aMWLjUttlPrfOniDWs35v62rOEIqP5GBzUvRswsNY8rr1g1KuDobqaRFGMCNnrtDmhzUN8y7QucrcwMY/+w==}
|
||||
@ -11824,8 +11838,8 @@ packages:
|
||||
wcwidth@1.0.1:
|
||||
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
|
||||
|
||||
web-vitals@4.2.2:
|
||||
resolution: {integrity: sha512-nYfoOqb4EmElljyXU2qdeE76KsvoHdftQKY4DzA9Aw8DervCg2bG634pHLrJ/d6+B4mE3nWTSJv8Mo7B2mbZkw==}
|
||||
web-vitals@4.2.4:
|
||||
resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==}
|
||||
|
||||
webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
@ -17930,6 +17944,8 @@ snapshots:
|
||||
dependencies:
|
||||
browserslist: 4.24.4
|
||||
|
||||
core-js@3.41.0: {}
|
||||
|
||||
core-util-is@1.0.3: {}
|
||||
|
||||
cors@2.8.5:
|
||||
@ -21583,15 +21599,16 @@ snapshots:
|
||||
dependencies:
|
||||
xtend: 4.0.2
|
||||
|
||||
posthog-js@1.149.2:
|
||||
posthog-js@1.234.9:
|
||||
dependencies:
|
||||
core-js: 3.41.0
|
||||
fflate: 0.4.8
|
||||
preact: 10.22.0
|
||||
web-vitals: 4.2.2
|
||||
web-vitals: 4.2.4
|
||||
|
||||
posthog-node@4.1.0:
|
||||
dependencies:
|
||||
axios: 1.7.4
|
||||
axios: 1.7.7
|
||||
rusha: 0.8.14
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
@ -23924,7 +23941,7 @@ snapshots:
|
||||
dependencies:
|
||||
defaults: 1.0.4
|
||||
|
||||
web-vitals@4.2.2: {}
|
||||
web-vitals@4.2.4: {}
|
||||
|
||||
webidl-conversions@3.0.1: {}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user