Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
58d512d
Team invitation (#171)
fomalhautb Aug 4, 2024
b766e4e
Allow Next.js version `latest` in package.json
N2D4 Aug 4, 2024
6011565
Fix typo
N2D4 Aug 4, 2024
52d489b
Update error message
N2D4 Aug 5, 2024
1dbf2b7
Remove unnecessary console.warn
N2D4 Aug 5, 2024
f0a5c32
Updated "edit this page" button
N2D4 Aug 5, 2024
e8221ca
Hide unsupported properties from docs
N2D4 Aug 6, 2024
ee139bf
OAuth token tests
N2D4 Aug 7, 2024
0739bca
Fix typo
N2D4 Aug 7, 2024
721c7bb
added create user button
fomalhautb Aug 8, 2024
a45e7a9
added create user button (#173)
fomalhautb Aug 8, 2024
b160362
added basic team settings
fomalhautb Aug 9, 2024
f1bd51a
Create SECURITY.md
N2D4 Aug 9, 2024
b0e4867
added editable text
fomalhautb Aug 9, 2024
291cd0d
added more team settings
fomalhautb Aug 9, 2024
951c075
Export button in tables
N2D4 Aug 9, 2024
56bdde3
Export all pages of tables
N2D4 Aug 9, 2024
fe5642d
Update security policy
N2D4 Aug 9, 2024
e4541b3
Fix docs typo
N2D4 Aug 9, 2024
51ab842
More docs typos
N2D4 Aug 9, 2024
7c6f87e
Improved user creation handlers
N2D4 Aug 9, 2024
1cb11ea
added list users on client
fomalhautb Aug 9, 2024
9c23406
updated team-settings
fomalhautb Aug 9, 2024
ada82eb
hide team setting component for now
fomalhautb Aug 9, 2024
e71d057
Merge branch 'dev' into team-components
fomalhautb Aug 9, 2024
2fef40b
Fix: Improve error handling for Server API (#170)
kfahad5607 Aug 9, 2024
9b8cec3
added ensureClientUserAuthenticated
fomalhautb Aug 9, 2024
0fb93c7
Merge branch 'dev' into team-components
fomalhautb Aug 9, 2024
5217be9
improved error handling
fomalhautb Aug 10, 2024
068ec4b
removed unused imports
fomalhautb Aug 10, 2024
d3d50c3
fixed bug
fomalhautb Aug 10, 2024
3ed995a
added member list
fomalhautb Aug 10, 2024
fccbcf1
Sign up restriction button on dashboard
N2D4 Aug 10, 2024
2fc3ea1
Merge branch 'dev' into team-components
fomalhautb Aug 10, 2024
5cfafde
moved data table to stack-ui
fomalhautb Aug 10, 2024
b99aeb3
added remove user modal
fomalhautb Aug 10, 2024
5e91a10
fixed chokidar
fomalhautb Aug 10, 2024
7c404d3
updated ui
fomalhautb Aug 10, 2024
5a0d52f
Merge branch 'dev' into team-components
fomalhautb Aug 11, 2024
055aaf1
fixed merge
fomalhautb Aug 11, 2024
ac20f43
fixed merge
fomalhautb Aug 11, 2024
362d1e1
fixed merge
fomalhautb Aug 11, 2024
3c141d4
updated settings component
fomalhautb Aug 11, 2024
dd2ef8d
improved mobile styles
fomalhautb Aug 11, 2024
74a02e3
added user invitation ui
fomalhautb Aug 11, 2024
7cbd494
added team creation page
fomalhautb Aug 11, 2024
8418e9d
added team creation to team component
fomalhautb Aug 11, 2024
d2ab331
added setting icon to team switcher
fomalhautb Aug 11, 2024
9b4fb72
added settings sections
fomalhautb Aug 11, 2024
80e5689
added client_team_creation_enabled
fomalhautb Aug 11, 2024
f8ac5cb
added frontend team creation enabled checks
fomalhautb Aug 11, 2024
296817a
updated demo page
fomalhautb Aug 11, 2024
8a807f8
Merge branch 'dev' into team-components
fomalhautb Aug 11, 2024
61fe75b
added member profile update
fomalhautb Aug 11, 2024
0cf5810
fixed profile editing
fomalhautb Aug 11, 2024
6eb94dc
added leave team button
fomalhautb Aug 11, 2024
77678c2
added create/delete team redirect
fomalhautb Aug 11, 2024
d2f0ed0
fixed column header, updated team setting
fomalhautb Aug 11, 2024
026bfb3
fixed account setting padding
fomalhautb Aug 11, 2024
6e49071
updated tests
fomalhautb Aug 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion 2 apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"typecheck": "tsc --noEmit",
"with-env": "dotenv -c development --",
"with-env:prod": "dotenv -c --",
"dev": "concurrently \"next dev --port 8102\" \"pnpm run watch-docs\" \"pnpm run prisma-studio\"",
"dev": "concurrently -k \"next dev --port 8102\" \"pnpm run watch-docs\" \"pnpm run prisma-studio\"",
"build": "pnpm run codegen && next build",
"analyze-bundle": "ANALYZE_BUNDLE=1 pnpm run build",
"start": "next start --port 8102",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- AlterTable
ALTER TABLE "ProjectConfig" ADD COLUMN "clientTeamCreationEnabled" BOOLEAN NOT NULL DEFAULT false;

-- Update existing rows
UPDATE "ProjectConfig" SET "clientTeamCreationEnabled" = false;

-- Remove the default constraint
ALTER TABLE "ProjectConfig" ALTER COLUMN "clientTeamCreationEnabled" DROP DEFAULT;
28 changes: 14 additions & 14 deletions 28 apps/backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ model ProjectConfig {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

allowLocalhost Boolean
signUpEnabled Boolean @default(true)
credentialEnabled Boolean
magicLinkEnabled Boolean

createTeamOnSignUp Boolean
allowLocalhost Boolean
signUpEnabled Boolean @default(true)
credentialEnabled Boolean
magicLinkEnabled Boolean
createTeamOnSignUp Boolean
clientTeamCreationEnabled Boolean

projects Project[]
oauthProviderConfigs OAuthProviderConfig[]
Expand Down Expand Up @@ -219,11 +219,11 @@ model ProjectUser {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

project Project @relation("ProjectUsers", fields: [projectId], references: [id], onDelete: Cascade)
projectUserRefreshTokens ProjectUserRefreshToken[]
projectUserAuthorizationCodes ProjectUserAuthorizationCode[]
projectUserOAuthAccounts ProjectUserOAuthAccount[]
teamMembers TeamMember[]
project Project @relation("ProjectUsers", fields: [projectId], references: [id], onDelete: Cascade)
projectUserRefreshTokens ProjectUserRefreshToken[]
projectUserAuthorizationCodes ProjectUserAuthorizationCode[]
projectUserOAuthAccounts ProjectUserOAuthAccount[]
teamMembers TeamMember[]

// @deprecated
projectUserEmailVerificationCode ProjectUserEmailVerificationCode[]
Expand All @@ -239,8 +239,8 @@ model ProjectUser {
passwordHash String?
authWithEmail Boolean

requiresTotpMfa Boolean @default(false)
totpSecret Bytes?
requiresTotpMfa Boolean @default(false)
totpSecret Bytes?

serverMetadata Json?
clientMetadata Json?
Expand Down Expand Up @@ -362,7 +362,7 @@ model VerificationCode {
usedAt DateTime?
redirectUrl String?

method Json @default("null")
method Json @default("null")

data Json

Expand Down
1 change: 1 addition & 0 deletions 1 apps/backend/prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ async function seed() {
credentialEnabled: true,
magicLinkEnabled: true,
createTeamOnSignUp: false,
clientTeamCreationEnabled: true,
},
},
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import * as yup from "yup";
import { yupObject, yupString, yupNumber, yupBoolean, yupArray, yupMixed } from "@stackframe/stack-shared/dist/schema-fields";
import { prismaClient } from "@/prisma-client";
import { sendEmailFromTemplate } from "@/lib/emails";
import { createAuthTokens } from "@/lib/tokens";
import { prismaClient } from "@/prisma-client";
import { createVerificationCodeHandler } from "@/route-handlers/verification-code-handler";
import { signInResponseSchema } from "@stackframe/stack-shared/dist/schema-fields";
import { VerificationCodeType } from "@prisma/client";
import { UsersCrud } from "@stackframe/stack-shared/dist/interface/crud/users";
import { sendEmailFromTemplate } from "@/lib/emails";
import { StackAssertionError } from "@stackframe/stack-shared/dist/utils/errors";
import { signInResponseSchema, yupBoolean, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { createMfaRequiredError } from "../../mfa/sign-in/verification-code-handler";

export const signInVerificationCodeHandler = createVerificationCodeHandler({
Expand Down
1 change: 1 addition & 0 deletions 1 apps/backend/src/app/api/v1/internal/projects/crud.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const internalProjectsCrudHandlers = createLazyProxy(() => createCrudHand
magicLinkEnabled: data.config?.magic_link_enabled ?? false,
allowLocalhost: data.config?.allow_localhost ?? true,
createTeamOnSignUp: data.config?.create_team_on_sign_up ?? false,
clientTeamCreationEnabled: data.config?.client_team_creation_enabled ?? false,
domains: data.config?.domains ? {
create: data.config.domains.map(item => ({
domain: item.domain,
Expand Down
1 change: 1 addition & 0 deletions 1 apps/backend/src/app/api/v1/projects/current/crud.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ export const projectsCrudHandlers = createLazyProxy(() => createCrudHandlers(pro
signUpEnabled: data.config?.sign_up_enabled,
credentialEnabled: data.config?.credential_enabled,
magicLinkEnabled: data.config?.magic_link_enabled,
clientTeamCreationEnabled: data.config?.client_team_creation_enabled,
allowLocalhost: data.config?.allow_localhost,
createTeamOnSignUp: data.config?.create_team_on_sign_up,
domains: data.config?.domains ? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export const teamInvitationCodeHandler = createVerificationCodeHandler({
tags: ["Teams"],
},
},
userRequired: true,
type: VerificationCodeType.TEAM_INVITATION,
data: yupObject({
team_id: yupString().required(),
Expand Down Expand Up @@ -60,6 +59,8 @@ export const teamInvitationCodeHandler = createVerificationCodeHandler({
});
},
async handler(project, {}, data, body, user) {
if (!user) throw new KnownErrors.UserAuthenticationRequired();

const oldMembership = await prismaClient.teamMember.findUnique({
where: {
projectId_projectUserId_teamId: {
Expand All @@ -86,6 +87,8 @@ export const teamInvitationCodeHandler = createVerificationCodeHandler({
};
},
async details(project, {}, data, body, user) {
if (!user) throw new KnownErrors.UserAuthenticationRequired();

const team = await teamsCrudHandlers.adminRead({
project,
team_id: data.team_id,
Expand Down
34 changes: 21 additions & 13 deletions 34 apps/backend/src/app/api/v1/team-member-profiles/crud.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ import { teamMemberProfilesCrud } from "@stackframe/stack-shared/dist/interface/
import { userIdOrMeSchema, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { StatusError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import { createLazyProxy } from "@stackframe/stack-shared/dist/utils/proxies";
import { userFullInclude, userPrismaToCrud } from "../users/crud";

const fullInclude = { projectUser: true };
const fullInclude = { projectUser: { include: userFullInclude } };

function prismaToCrud(prisma: Prisma.TeamMemberGetPayload<{ include: typeof fullInclude }>) {
return {
team_id: prisma.teamId,
user_id: prisma.projectUserId,
display_name: prisma.displayName ?? prisma.projectUser.displayName,
profile_image_url: prisma.profileImageUrl ?? prisma.projectUser.profileImageUrl,
user: userPrismaToCrud(prisma.projectUser),
};
}

Expand All @@ -37,7 +39,7 @@ export const teamMemberProfilesCrudHandlers = createLazyProxy(() => createCrudHa
// - list users in their own team if they have the $read_members permission
// - list their own profile

const currentUserId = auth.user?.id ?? throwErr("Client must be authenticated");
const currentUserId = auth.user?.id ?? throwErr(new KnownErrors.CannotGetOwnUserWithoutUser());

if (!query.team_id) {
throw new StatusError(StatusError.BadRequest, 'team_id is required for access type client');
Expand Down Expand Up @@ -85,14 +87,17 @@ export const teamMemberProfilesCrudHandlers = createLazyProxy(() => createCrudHa
return await prismaClient.$transaction(async (tx) => {
const userId = getIdFromUserIdOrMe(params.user_id, auth.user);

if (auth.type === 'client' && userId !== auth.user?.id) {
await ensureUserTeamPermissionExists(tx, {
project: auth.project,
teamId: params.team_id,
userId: auth.user?.id ?? throwErr("Client must be authenticated"),
permissionId: '$read_members',
errorType: 'required',
});
if (auth.type === 'client') {
const currentUserId = auth.user?.id ?? throwErr(new KnownErrors.CannotGetOwnUserWithoutUser());
if (userId !== currentUserId) {
await ensureUserTeamPermissionExists(tx, {
project: auth.project,
teamId: params.team_id,
userId: currentUserId,
permissionId: '$read_members',
errorType: 'required',
});
}
}

await ensureTeamMembershipExists(tx, { projectId: auth.project.id, teamId: params.team_id, userId: userId });
Expand Down Expand Up @@ -120,14 +125,17 @@ export const teamMemberProfilesCrudHandlers = createLazyProxy(() => createCrudHa
return await prismaClient.$transaction(async (tx) => {
const userId = getIdFromUserIdOrMe(params.user_id, auth.user);

if (auth.type === 'client' && userId !== auth.user?.id) {
throw new StatusError(StatusError.Forbidden, 'Cannot update another user\'s profile');
if (auth.type === 'client') {
const currentUserId = auth.user?.id ?? throwErr(new KnownErrors.CannotGetOwnUserWithoutUser());
if (userId !== currentUserId) {
throw new StatusError(StatusError.Forbidden, 'Cannot update another user\'s profile');
}
}

await ensureTeamMembershipExists(tx, {
projectId: auth.project.id,
teamId: params.team_id,
userId: auth.user?.id ?? throwErr("Client must be authenticated"),
userId,
});

const db = await tx.teamMember.update({
Expand Down
20 changes: 12 additions & 8 deletions 20 apps/backend/src/app/api/v1/team-memberships/crud.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,18 @@ export const teamMembershipsCrudHandlers = createLazyProxy(() => createCrudHandl

// Users are always allowed to remove themselves from a team
// Only users with the $remove_members permission can remove other users
if (auth.type === 'client' && userId !== auth.user?.id) {
await ensureUserTeamPermissionExists(tx, {
project: auth.project,
teamId: params.team_id,
userId: auth.user?.id ?? throwErr('auth.user is null'),
permissionId: "$remove_members",
errorType: 'required',
});
if (auth.type === 'client') {
const currentUserId = auth.user?.id ?? throwErr(new KnownErrors.CannotGetOwnUserWithoutUser());

if (userId !== currentUserId) {
await ensureUserTeamPermissionExists(tx, {
project: auth.project,
teamId: params.team_id,
userId: auth.user?.id ?? throwErr('auth.user is null'),
permissionId: "$remove_members",
errorType: 'required',
});
}
}

await ensureTeamMembershipExists(tx, {
Expand Down
11 changes: 8 additions & 3 deletions 11 apps/backend/src/app/api/v1/team-permissions/crud.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { ensureTeamMembershipExists, ensureUserTeamPermissionExists } from "@/li
import { prismaClient } from "@/prisma-client";
import { createCrudHandlers } from "@/route-handlers/crud-handler";
import { getIdFromUserIdOrMe } from "@/route-handlers/utils";
import { KnownErrors } from "@stackframe/stack-shared";
import { teamPermissionsCrud } from '@stackframe/stack-shared/dist/interface/crud/team-permissions';
import { teamPermissionDefinitionIdSchema, userIdOrMeSchema, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { StatusError } from "@stackframe/stack-shared/dist/utils/errors";
import { StatusError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import { createLazyProxy } from "@stackframe/stack-shared/dist/utils/proxies";

export const teamPermissionsCrudHandlers = createLazyProxy(() => createCrudHandlers(teamPermissionsCrud, {
Expand Down Expand Up @@ -52,8 +53,12 @@ export const teamPermissionsCrudHandlers = createLazyProxy(() => createCrudHandl
},
async onList({ auth, query }) {
const userId = getIdFromUserIdOrMe(query.user_id, auth.user);
if (auth.type === 'client' && userId !== auth.user?.id) {
throw new StatusError(StatusError.Forbidden, 'Client can only list permissions for their own user. user_id must be either "me" or the ID of the current user');
if (auth.type === 'client') {
const currentUserId = auth.user?.id || throwErr(new KnownErrors.CannotGetOwnUserWithoutUser());

if (userId !== currentUserId) {
throw new StatusError(StatusError.Forbidden, 'Client can only list permissions for their own user. user_id must be either "me" or the ID of the current user');
}
}

return await prismaClient.$transaction(async (tx) => {
Expand Down
22 changes: 17 additions & 5 deletions 22 apps/backend/src/app/api/v1/teams/crud.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ export const teamsCrudHandlers = createLazyProxy(() => createCrudHandlers(teamsC
team_id: yupString().uuid().required(),
}),
onCreate: async ({ query, auth, data }) => {
if (auth.type === 'client' && !auth.user) {
throw new KnownErrors.UserAuthenticationRequired();
}

if (auth.type === 'client' && !auth.project.config.client_team_creation_enabled) {
throw new StatusError(StatusError.Forbidden, 'Client team creation is disabled for this project');
}

const db = await prismaClient.$transaction(async (tx) => {
const db = await tx.team.create({
data: {
Expand Down Expand Up @@ -69,7 +77,7 @@ export const teamsCrudHandlers = createLazyProxy(() => createCrudHandlers(teamsC
await ensureTeamMembershipExists(tx, {
projectId: auth.project.id,
teamId: params.team_id,
userId: auth.user?.id ?? throwErr('auth.user is null'),
userId: auth.user?.id ?? throwErr(new KnownErrors.UserAuthenticationRequired()),
});
}

Expand Down Expand Up @@ -97,7 +105,7 @@ export const teamsCrudHandlers = createLazyProxy(() => createCrudHandlers(teamsC
await ensureUserTeamPermissionExists(tx, {
project: auth.project,
teamId: params.team_id,
userId: auth.user?.id ?? throwErr('auth.user is null'),
userId: auth.user?.id ?? throwErr(new KnownErrors.UserAuthenticationRequired()),
permissionId: "$update_team",
errorType: 'required',
});
Expand Down Expand Up @@ -134,7 +142,7 @@ export const teamsCrudHandlers = createLazyProxy(() => createCrudHandlers(teamsC
await ensureUserTeamPermissionExists(tx, {
project: auth.project,
teamId: params.team_id,
userId: auth.user?.id ?? throwErr('auth.user is null'),
userId: auth.user?.id ?? throwErr(new KnownErrors.UserAuthenticationRequired()),
permissionId: "$delete_team",
errorType: 'required',
});
Expand All @@ -160,8 +168,12 @@ export const teamsCrudHandlers = createLazyProxy(() => createCrudHandlers(teamsC
},
onList: async ({ query, auth }) => {
const userId = getIdFromUserIdOrMe(query.user_id, auth.user);
if (auth.type === 'client' && userId !== auth.user?.id) {
throw new StatusError(StatusError.Forbidden, 'Client can only list teams for their own user. user_id must be either "me" or the ID of the current user');
if (auth.type === 'client') {
const currentUserId = auth.user?.id || throwErr(new KnownErrors.CannotGetOwnUserWithoutUser());

if (userId !== currentUserId) {
throw new StatusError(StatusError.Forbidden, 'Client can only list teams for their own user. user_id must be either "me" or the ID of the current user');
}
}

const db = await prismaClient.team.findMany({
Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.