Skip to content

Commit c64fbf4

Browse files
committed
Sign up restriction button on dashboard
Fix stack-auth#66, stack-auth#74
1 parent 4792aa5 commit c64fbf4

File tree

37 files changed

+429
-124
lines changed

37 files changed

+429
-124
lines changed

.github/workflows/publish-docs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
run:
1010
runs-on: ubuntu-latest
1111
env:
12-
NEXT_PUBLIC_STACK_URL: http://localhost:8101
12+
NEXT_PUBLIC_STACK_URL: http://localhost:8102
1313
NEXT_PUBLIC_STACK_PROJECT_ID: internal
1414
NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY: internal-project-publishable-client-key
1515
STACK_SECRET_SERVER_KEY: internal-project-secret-server-key

apps/backend/.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,4 @@ STACK_SVIX_API_KEY=# enter the API key for the Svix webhook service here. Use `e
3737

3838
# Misc, optional
3939
STACK_ACCESS_TOKEN_EXPIRATION_TIME=# enter the expiration time for the access token here. Optional, don't specify it for default value
40+
STACK_SETUP_ADMIN_GITHUB_ID=# enter the account ID of the admin user here, and after running the seed script they will be able to access the internal project in the Stack dashboard. Optional, don't specify it for default value
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- AlterTable
2+
ALTER TABLE "ProjectConfig" ADD COLUMN "signUpEnabled" BOOLEAN NOT NULL DEFAULT true;

apps/backend/prisma/schema.prisma

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ model ProjectConfig {
4040
updatedAt DateTime @updatedAt
4141
4242
allowLocalhost Boolean
43+
signUpEnabled Boolean @default(true)
4344
credentialEnabled Boolean
4445
magicLinkEnabled Boolean
4546

apps/backend/prisma/seed.ts

Lines changed: 90 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,110 @@
11
import { PrismaClient } from '@prisma/client';
2-
import { getEnvVariable } from '@stackframe/stack-shared/dist/utils/env';
2+
import { throwErr } from '@stackframe/stack-shared/dist/utils/errors';
33
const prisma = new PrismaClient();
44

55

66
async function seed() {
77
console.log('Seeding database...');
8-
9-
const oldProjects = await prisma.project.findUnique({
8+
9+
const oldProject = await prisma.project.findUnique({
1010
where: {
1111
id: 'internal',
1212
},
1313
});
1414

15-
if (oldProjects) {
16-
console.log('Internal project already exists, skipping seeding');
17-
return;
18-
}
19-
20-
await prisma.project.upsert({
21-
where: {
22-
id: 'internal',
23-
},
24-
create: {
25-
id: 'internal',
26-
displayName: 'Stack Dashboard',
27-
description: 'Stack\'s admin dashboard',
28-
isProductionMode: false,
29-
apiKeySets: {
30-
create: [{
31-
description: "Internal API key set",
32-
publishableClientKey: "this-publishable-client-key-is-for-local-development-only",
33-
secretServerKey: "this-secret-server-key-is-for-local-development-only",
34-
superSecretAdminKey: "this-super-secret-admin-key-is-for-local-development-only",
35-
expiresAt: new Date('2099-12-31T23:59:59Z'),
36-
}],
15+
let createdProject;
16+
if (oldProject) {
17+
console.log('Internal project already exists, skipping its creation');
18+
} else {
19+
createdProject = await prisma.project.upsert({
20+
where: {
21+
id: 'internal',
3722
},
38-
config: {
39-
create: {
40-
allowLocalhost: true,
41-
oauthProviderConfigs: {
42-
create: (['github', 'facebook', 'google', 'microsoft'] as const).map((id) => ({
43-
id,
44-
proxiedOAuthConfig: {
45-
create: {
46-
type: id.toUpperCase() as any,
23+
create: {
24+
id: 'internal',
25+
displayName: 'Stack Dashboard',
26+
description: 'Stack\'s admin dashboard',
27+
isProductionMode: false,
28+
apiKeySets: {
29+
create: [{
30+
description: "Internal API key set",
31+
publishableClientKey: "this-publishable-client-key-is-for-local-development-only",
32+
secretServerKey: "this-secret-server-key-is-for-local-development-only",
33+
superSecretAdminKey: "this-super-secret-admin-key-is-for-local-development-only",
34+
expiresAt: new Date('2099-12-31T23:59:59Z'),
35+
}],
36+
},
37+
config: {
38+
create: {
39+
allowLocalhost: true,
40+
oauthProviderConfigs: {
41+
create: (['github', 'facebook', 'google', 'microsoft'] as const).map((id) => ({
42+
id,
43+
proxiedOAuthConfig: {
44+
create: {
45+
type: id.toUpperCase() as any,
46+
}
47+
},
48+
projectUserOAuthAccounts: {
49+
create: []
50+
}
51+
})),
52+
},
53+
emailServiceConfig: {
54+
create: {
55+
proxiedEmailServiceConfig: {
56+
create: {}
4757
}
48-
},
49-
projectUserOAuthAccounts: {
50-
create: []
51-
}
52-
})),
53-
},
54-
emailServiceConfig: {
55-
create: {
56-
proxiedEmailServiceConfig: {
57-
create: {}
5858
}
59-
}
59+
},
60+
credentialEnabled: true,
61+
magicLinkEnabled: true,
62+
createTeamOnSignUp: false,
6063
},
61-
credentialEnabled: true,
62-
magicLinkEnabled: true,
63-
createTeamOnSignUp: false,
6464
},
6565
},
66-
},
67-
update: {},
68-
});
69-
console.log('Internal project created');
66+
update: {},
67+
});
68+
console.log('Internal project created');
69+
}
70+
71+
// eslint-disable-next-line no-restricted-syntax
72+
const adminGithubId = process.env.STACK_SETUP_ADMIN_GITHUB_ID;
73+
if (adminGithubId) {
74+
console.log("Found admin GitHub ID in environment variables, creating admin user...");
75+
await prisma.projectUser.upsert({
76+
where: {
77+
projectId_projectUserId: {
78+
projectId: 'internal',
79+
projectUserId: '707156c3-0d1b-48cf-b09d-3171c7f613d5',
80+
},
81+
},
82+
create: {
83+
projectId: 'internal',
84+
projectUserId: '707156c3-0d1b-48cf-b09d-3171c7f613d5',
85+
displayName: 'Admin user generated by seed script',
86+
primaryEmailVerified: false,
87+
authWithEmail: false,
88+
serverMetadata: {
89+
managedProjectIds: [
90+
"internal",
91+
],
92+
},
93+
projectUserOAuthAccounts: {
94+
create: [{
95+
providerAccountId: adminGithubId,
96+
projectConfigId: createdProject?.configId ?? oldProject?.configId ?? throwErr('No internal project config ID found'),
97+
oauthProviderConfigId: 'github',
98+
}],
99+
},
100+
},
101+
update: {},
102+
});
103+
console.log(`Admin user created (if it didn't already exist)`);
104+
} else {
105+
console.log('No admin GitHub ID found in environment variables, skipping admin user creation');
106+
}
107+
70108
console.log('Seeding complete!');
71109
}
72110

apps/backend/src/app/api/v1/auth/oauth/callback/[provider_id]/route.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,10 @@ export const GET = createSmartRouteHandler({
247247

248248
// ========================== sign up user ==========================
249249

250-
const newAccount = await usersCrudHandlers.serverCreate({
250+
if (!project.config.sign_up_enabled) {
251+
throw new KnownErrors.SignUpNotEnabled();
252+
}
253+
const newAccount = await usersCrudHandlers.adminCreate({
251254
project,
252255
data: {
253256
display_name: userInfo.displayName,
@@ -260,7 +263,7 @@ export const GET = createSmartRouteHandler({
260263
account_id: userInfo.accountId,
261264
email: userInfo.email,
262265
}],
263-
}
266+
},
264267
});
265268
await storeTokens();
266269
return {

apps/backend/src/app/api/v1/auth/otp/send-sign-in-code/route.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ export const POST = createSmartRouteHandler({
4545

4646
const userPrisma = usersPrisma.length > 0 ? usersPrisma[0] : null;
4747
const isNewUser = !userPrisma;
48+
if (isNewUser && !project.config.sign_up_enabled) {
49+
throw new KnownErrors.SignUpNotEnabled();
50+
}
51+
4852
let userObj: Pick<NonNullable<typeof userPrisma>, "projectUserId" | "displayName" | "primaryEmail"> | null = userPrisma;
4953
if (!userObj) {
5054
// TODO this should be in the same transaction as the read above

apps/backend/src/app/api/v1/auth/password/sign-up/route.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ export const POST = createSmartRouteHandler({
4545
throw passwordError;
4646
}
4747

48+
if (!project.config.sign_up_enabled) {
49+
throw new KnownErrors.SignUpNotEnabled();
50+
}
51+
4852
const createdUser = await usersCrudHandlers.adminCreate({
4953
project,
5054
data: {

apps/backend/src/app/api/v1/internal/projects/crud.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,14 @@ export const internalProjectsCrudHandlers = createLazyProxy(() => createCrudHand
3131
id: generateUuid(),
3232
displayName: data.display_name,
3333
description: data.description,
34-
isProductionMode: data.is_production_mode || false,
34+
isProductionMode: data.is_production_mode ?? false,
3535
config: {
3636
create: {
37-
credentialEnabled: data.config?.credential_enabled || true,
38-
magicLinkEnabled: data.config?.magic_link_enabled || false,
39-
allowLocalhost: data.config?.allow_localhost || true,
40-
createTeamOnSignUp: data.config?.create_team_on_sign_up || false,
37+
signUpEnabled: data.config?.sign_up_enabled ?? true,
38+
credentialEnabled: data.config?.credential_enabled ?? true,
39+
magicLinkEnabled: data.config?.magic_link_enabled ?? false,
40+
allowLocalhost: data.config?.allow_localhost ?? true,
41+
createTeamOnSignUp: data.config?.create_team_on_sign_up ?? false,
4142
domains: data.config?.domains ? {
4243
create: data.config.domains.map(item => ({
4344
domain: item.domain,

apps/backend/src/app/api/v1/projects/current/crud.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ export const projectsCrudHandlers = createLazyProxy(() => createCrudHandlers(pro
253253
isProductionMode: data.is_production_mode,
254254
config: {
255255
update: {
256+
signUpEnabled: data.config?.sign_up_enabled,
256257
credentialEnabled: data.config?.credential_enabled,
257258
magicLinkEnabled: data.config?.magic_link_enabled,
258259
allowLocalhost: data.config?.allow_localhost,

0 commit comments

Comments
 (0)