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
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
Warnings:

- The values [SPOTIFY] on the enum `ProxiedOAuthProviderType` will be removed. If these variants are still used in the database, this will fail.

*/
-- AlterEnum
BEGIN;
CREATE TYPE "ProxiedOAuthProviderType_new" AS ENUM ('GITHUB', 'FACEBOOK', 'GOOGLE', 'MICROSOFT');
ALTER TABLE "ProxiedOAuthProviderConfig" ALTER COLUMN "type" TYPE "ProxiedOAuthProviderType_new" USING ("type"::text::"ProxiedOAuthProviderType_new");
ALTER TYPE "ProxiedOAuthProviderType" RENAME TO "ProxiedOAuthProviderType_old";
ALTER TYPE "ProxiedOAuthProviderType_new" RENAME TO "ProxiedOAuthProviderType";
DROP TYPE "ProxiedOAuthProviderType_old";
COMMIT;
1 change: 0 additions & 1 deletion 1 apps/backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,6 @@ enum ProxiedOAuthProviderType {
FACEBOOK
GOOGLE
MICROSOFT
SPOTIFY
}

model StandardOAuthProviderConfig {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { decodeAccessToken, oauthCookieSchema } from "@/lib/tokens";
import { getProvider } from "@/oauth";
import { prismaClient } from "@/prisma-client";
import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler";
import { sharedProviders } from "@stackframe/stack-shared/dist/interface/clientInterface";
import { KnownErrors } from "@stackframe/stack-shared/dist/known-errors";
import { yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { getNodeEnvironment } from "@stackframe/stack-shared/dist/utils/env";
Expand Down Expand Up @@ -77,7 +76,7 @@ export const GET = createSmartRouteHandler({
throw new StatusError(StatusError.Forbidden, "The access token is not valid for this project");
}

if (query.provider_scope && sharedProviders.includes(provider.type as any)) {
if (query.provider_scope && provider.type === "shared") {
throw new KnownErrors.OAuthExtraScopeNotAvailableWithSharedOAuthKeys();
}
projectUserId = userId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createCrudHandlers } from "@/route-handlers/crud-handler";
import { KnownErrors } from "@stackframe/stack-shared";
import { providerAccessTokenCrud } from "@stackframe/stack-shared/dist/interface/crud/oauth";
import { yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { StackAssertionError, StatusError } from "@stackframe/stack-shared/dist/utils/errors";
import { StackAssertionError } from "@stackframe/stack-shared/dist/utils/errors";
import { extractScopes } from "@stackframe/stack-shared/dist/utils/strings";


Expand Down
7 changes: 4 additions & 3 deletions 7 apps/backend/src/app/api/v1/internal/projects/crud.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { fullProjectInclude, listManagedProjectIds, projectPrismaToCrud } from "@/lib/projects";
import { ensureSharedProvider, ensureStandardProvider } from "@/lib/request-checks";
import { prismaClient } from "@/prisma-client";
import { createCrudHandlers } from "@/route-handlers/crud-handler";
import { KnownErrors } from "@stackframe/stack-shared";
import { internalProjectsCrud } from "@stackframe/stack-shared/dist/interface/crud/projects";
import { projectIdSchema, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { projectIdSchema, yupObject } from "@stackframe/stack-shared/dist/schema-fields";
import { StackAssertionError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import { createLazyProxy } from "@stackframe/stack-shared/dist/utils/proxies";
import { typedToUppercase } from "@stackframe/stack-shared/dist/utils/strings";
Expand Down Expand Up @@ -49,12 +50,12 @@ export const internalProjectsCrudHandlers = createLazyProxy(() => createCrudHand
enabled: item.enabled,
proxiedOAuthConfig: item.type === "shared" ? {
create: {
type: typedToUppercase(item.id),
type: typedToUppercase(ensureSharedProvider(item.id)),
}
} : undefined,
standardOAuthConfig: item.type === "standard" ? {
create: {
type: typedToUppercase(item.id),
type: typedToUppercase(ensureStandardProvider(item.id)),
clientId: item.client_id ?? throwErr('client_id is required'),
clientSecret: item.client_secret ?? throwErr('client_secret is required'),
}
Expand Down
10 changes: 6 additions & 4 deletions 10 apps/backend/src/app/api/v1/projects/current/crud.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { isTeamSystemPermission, listTeamPermissionDefinitions, teamSystemPermissionStringToDBType } from "@/lib/permissions";
import { fullProjectInclude, projectPrismaToCrud } from "@/lib/projects";
import { ensureSharedProvider } from "@/lib/request-checks";
import { prismaClient } from "@/prisma-client";
import { createCrudHandlers } from "@/route-handlers/crud-handler";
import { projectsCrud } from "@stackframe/stack-shared/dist/interface/crud/projects";
import { yupObject } 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 { typedToUppercase } from "@stackframe/stack-shared/dist/utils/strings";
import { ensureStandardProvider } from "../../../../../lib/request-checks";

export const projectsCrudHandlers = createLazyProxy(() => createCrudHandlers(projectsCrud, {
paramsSchema: yupObject({}),
Expand Down Expand Up @@ -180,15 +182,15 @@ export const projectsCrudHandlers = createLazyProxy(() => createCrudHandlers(pro
providerConfigUpdate = {
proxiedOAuthConfig: {
create: {
type: typedToUppercase(providerUpdate.id),
type: typedToUppercase(ensureSharedProvider(providerUpdate.id)),
},
},
};
} else {
providerConfigUpdate = {
standardOAuthConfig: {
create: {
type: typedToUppercase(providerUpdate.id),
type: typedToUppercase(ensureStandardProvider(providerUpdate.id)),
clientId: providerUpdate.client_id ?? throwErr('client_id is required'),
clientSecret: providerUpdate.client_secret ?? throwErr('client_secret is required'),
},
Expand All @@ -212,15 +214,15 @@ export const projectsCrudHandlers = createLazyProxy(() => createCrudHandlers(pro
providerConfigData = {
proxiedOAuthConfig: {
create: {
type: typedToUppercase(provider.update.id),
type: typedToUppercase(ensureSharedProvider(provider.update.id)),
},
},
};
} else {
providerConfigData = {
standardOAuthConfig: {
create: {
type: typedToUppercase(provider.update.id),
type: typedToUppercase(ensureStandardProvider(provider.update.id)),
clientId: provider.update.client_id ?? throwErr('client_id is required'),
clientSecret: provider.update.client_secret ?? throwErr('client_secret is required'),
},
Expand Down
5 changes: 3 additions & 2 deletions 5 apps/backend/src/lib/projects.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { usersCrudHandlers } from "@/app/api/v1/users/crud";
import { prismaClient } from "@/prisma-client";
import { CrudHandlerInvocationError } from "@/route-handlers/crud-handler";
import { Prisma, ProxiedOAuthProviderType } from "@prisma/client";
import { Prisma } from "@prisma/client";
import { KnownErrors } from "@stackframe/stack-shared";
import { ProjectsCrud } from "@stackframe/stack-shared/dist/interface/crud/projects";
import { UsersCrud } from "@stackframe/stack-shared/dist/interface/crud/users";
import { StackAssertionError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import { ProviderType } from "@stackframe/stack-shared/dist/utils/oauth";
import { typedToLowercase } from "@stackframe/stack-shared/dist/utils/strings";
import { fullPermissionInclude, teamPermissionDefinitionJsonFromDbType, teamPermissionDefinitionJsonFromTeamSystemDbType } from "./permissions";
import { decodeAccessToken } from "./tokens";
Expand Down Expand Up @@ -61,7 +62,7 @@ export function projectPrismaToCrud(
): ProjectsCrud["Admin"]["Read"] {
const oauthProviders = prisma.config.oauthProviderConfigs
.flatMap((provider): {
id: Lowercase<ProxiedOAuthProviderType>,
id: ProviderType,
enabled: boolean,
type: 'standard' | 'shared',
client_id?: string | undefined,
Expand Down
24 changes: 22 additions & 2 deletions 24 apps/backend/src/lib/request-checks.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { ProxiedOAuthProviderType, StandardOAuthProviderType } from "@prisma/client";
import { KnownErrors } from "@stackframe/stack-shared";
import { PrismaTransaction } from "./types";
import { TeamSystemPermission, listUserTeamPermissions } from "./permissions";
import { ProjectsCrud } from "@stackframe/stack-shared/dist/interface/crud/projects";
import { ProviderType, sharedProviders, standardProviders } from "@stackframe/stack-shared/dist/utils/oauth";
import { TeamSystemPermission, listUserTeamPermissions } from "./permissions";
import { PrismaTransaction } from "./types";


async function _getTeamMembership(
Expand Down Expand Up @@ -122,4 +124,22 @@ export async function ensureUserExist(
if (!user) {
throw new KnownErrors.UserNotFound();
}
}

export function ensureSharedProvider(
providerId: ProviderType
): Lowercase<ProxiedOAuthProviderType> {
if (!sharedProviders.includes(providerId as any)) {
throw new KnownErrors.InvalidSharedOAuthProviderId(providerId);
}
return providerId as any;
}

export function ensureStandardProvider(
providerId: ProviderType
): Lowercase<StandardOAuthProviderType> {
if (!standardProviders.includes(providerId as any)) {
throw new KnownErrors.InvalidStandardOAuthProviderId(providerId);
}
return providerId as any;
}
1 change: 0 additions & 1 deletion 1 apps/dashboard/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,6 @@ enum ProxiedOAuthProviderType {
FACEBOOK
GOOGLE
MICROSOFT
SPOTIFY
}

model StandardOAuthProviderConfig {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"use client";
import { useAdminApp } from "../use-admin-app";
import { ProviderSettingSwitch, availableProviders } from "./providers";
import { PageLayout } from "../page-layout";
import { SettingCard, SettingSwitch } from "@/components/settings";
import { allProviders } from "@stackframe/stack-shared/dist/utils/oauth";
import { PageLayout } from "../page-layout";
import { useAdminApp } from "../use-admin-app";
import { ProviderSettingSwitch } from "./providers";

export default function PageClient() {
const stackAdminApp = useAdminApp();
Expand Down Expand Up @@ -37,7 +38,7 @@ export default function PageClient() {
</SettingCard>

<SettingCard title="OAuth Providers" description={`The "Sign in with XYZ" buttons on your app.`}>
{availableProviders.map((id) => {
{allProviders.map((id) => {
const provider = oauthProviders.find((provider) => provider.id === id);
return <ProviderSettingSwitch
key={id}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { InlineCode } from "@/components/ui/inline-code";
import { Label } from "@/components/ui/label";
import Typography from "@/components/ui/typography";
import { AdminProject } from "@stackframe/stack";
import { sharedProviders, standardProviders } from "@stackframe/stack-shared/dist/utils/oauth";
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
import { useState } from "react";
import * as yup from "yup";
Expand All @@ -19,7 +20,6 @@ type Props = {
updateProvider: (provider: AdminProject['config']['oauthProviders'][number]) => Promise<void>,
};

export const availableProviders = ['github', 'google', 'facebook', 'microsoft', 'spotify'] as const;
function toTitle(id: string) {
return {
github: "GitHub",
Expand Down Expand Up @@ -48,9 +48,10 @@ export const providerFormSchema = yup.object({

export type ProviderFormValues = yup.InferType<typeof providerFormSchema>

export function ProviderSettingDialog(props: Props) {
export function ProviderSettingDialog(props: Props & { open: boolean, onClose: () => void }) {
const hasSharedKeys = sharedProviders.includes(props.id as any);
const defaultValues = {
shared: props.provider?.type === 'shared',
shared: props.provider ? (props.provider.type === 'shared') : hasSharedKeys,
clientId: (props.provider as any)?.clientId ?? "",
clientSecret: (props.provider as any)?.clientSecret ?? "",
};
Expand All @@ -74,17 +75,22 @@ export function ProviderSettingDialog(props: Props) {
defaultValues={defaultValues}
formSchema={providerFormSchema}
onSubmit={onSubmit}
trigger={<SettingIconButton />}
open={props.open}
onClose={props.onClose}
title={`${toTitle(props.id)} OAuth provider`}
cancelButton
okButton={{ label: 'Save' }}
render={(form) => (
<>
<SwitchField
control={form.control}
name="shared"
label="Shared keys"
/>
{hasSharedKeys ?
<SwitchField
control={form.control}
name="shared"
label="Shared keys"
/> :
<Typography variant="secondary" type="footnote">
This OAuth provider does not support shared keys
</Typography>}

{form.watch("shared") ?
<Typography variant="secondary" type="footnote">
Expand Down Expand Up @@ -155,6 +161,7 @@ export function ProviderSettingSwitch(props: Props) {
const enabled = !!props.provider?.enabled;
const isShared = props.provider?.type === 'shared';
const [TurnOffProviderDialogOpen, setTurnOffProviderDialogOpen] = useState(false);
const [ProviderSettingDialogOpen, setProviderSettingDialogOpen] = useState(false);

const updateProvider = async (checked: boolean) => {
await props.updateProvider({
Expand Down Expand Up @@ -184,10 +191,10 @@ export function ProviderSettingSwitch(props: Props) {
setTurnOffProviderDialogOpen(true);
return;
} else {
await updateProvider(checked);
setProviderSettingDialogOpen(true);
}
}}
actions={<ProviderSettingDialog {...props} />}
actions={<SettingIconButton onClick={() => setProviderSettingDialogOpen(true)} />}
onlyShowActionsWhenChecked
/>

Expand All @@ -197,6 +204,8 @@ export function ProviderSettingSwitch(props: Props) {
providerId={props.id}
onConfirm={() => runAsynchronously(updateProvider(false))}
/>

<ProviderSettingDialog {...props} open={ProviderSettingDialogOpen} onClose={() => setProviderSettingDialogOpen(false)} />
</>
);
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { getProvider } from "@/oauth";
import { prismaClient } from "@/prisma-client";
import { createCrudHandlers } from "@/route-handlers/crud-handler";
import { sharedProviders } from "@/temporary-types";
import { KnownErrors } from "@stackframe/stack-shared";
import { sharedProviders } from "@stackframe/stack-shared/dist/interface/clientInterface";
import { accessTokenCrud } from "@stackframe/stack-shared/dist/interface/crud-deprecated/oauth";
import { StackAssertionError, StatusError } from "@stackframe/stack-shared/dist/utils/errors";
import { extractScopes } from "@stackframe/stack-shared/dist/utils/strings";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { getProject } from "@/lib/projects";
import { checkApiKeySet } from "@/lib/api-keys";
import { KnownErrors } from "@stackframe/stack-shared";
import { decodeAccessToken, oauthCookieSchema } from "@/lib/tokens";
import { sharedProviders } from "@stackframe/stack-shared/dist/interface/clientInterface";
import { prismaClient } from "@/prisma-client";
import { sharedProviders } from "@/temporary-types";


const expireMinutes = 10;
Expand Down
13 changes: 6 additions & 7 deletions 13 apps/dashboard/src/components/data-table/user-table.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
'use client';
import { useAdminApp } from '@/app/(main)/(protected)/projects/[projectId]/use-admin-app';
import { ServerUser } from '@stackframe/stack';
import { standardProviders } from "@stackframe/stack-shared/dist/interface/clientInterface";
import { jsonStringOrEmptySchema, jsonStringSchema } from "@stackframe/stack-shared/dist/schema-fields";
import { jsonStringOrEmptySchema } from "@stackframe/stack-shared/dist/schema-fields";
import { allProviders } from '@stackframe/stack-shared/dist/utils/oauth';
import { deindent } from '@stackframe/stack-shared/dist/utils/strings';
import { ColumnDef, Row, Table } from "@tanstack/react-table";
import { useMemo, useState } from "react";
import * as yup from "yup";
import { ActionDialog } from "../action-dialog";
import { CopyField } from '../copy-field';
import { FormDialog } from "../form-dialog";
import { DateField, InputField, SwitchField, TextAreaField } from "../form-fields";
import { SimpleTooltip } from "../simple-tooltip";
Expand All @@ -16,10 +19,6 @@ import { DataTable } from "./elements/data-table";
import { DataTableFacetedFilter } from "./elements/faceted-filter";
import { SearchToolbarItem } from "./elements/toolbar-items";
import { arrayFilterFn, standardFilterFn } from "./elements/utils";
import { wait } from '@stackframe/stack-shared/dist/utils/promises';
import { CopyField } from '../copy-field';
import { deindent } from '@stackframe/stack-shared/dist/utils/strings';
import { useAdminApp } from '@/app/(main)/(protected)/projects/[projectId]/use-admin-app';

export type ExtendedServerUser = ServerUser & {
authTypes: string[],
Expand All @@ -33,7 +32,7 @@ function userToolbarRender<TData>(table: Table<TData>) {
<DataTableFacetedFilter
column={table.getColumn("authTypes")}
title="Auth Method"
options={['email', 'password', ...standardProviders].map((provider) => ({
options={['email', 'password', ...allProviders].map((provider) => ({
value: provider,
label: provider,
}))}
Expand Down
18 changes: 8 additions & 10 deletions 18 apps/dashboard/src/lib/projects.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import * as yup from "yup";
import { OAuthProviderConfigJson, ProjectJson, ServerUserJson, EmailConfigJson } from "@/temporary-types";
import { Prisma, ProxiedOAuthProviderType, StandardOAuthProviderType } from "@prisma/client";
import { prismaClient } from "@/prisma-client";
import { EmailConfigJson, OAuthProviderConfigJson, OAuthProviderUpdateOptions, ProjectJson, ProjectUpdateOptions, ServerUserJson, SharedProvider, StandardProvider, sharedProviders, standardProviders } from "@/temporary-types";
import { Prisma, ProxiedOAuthProviderType, StandardOAuthProviderType } from "@prisma/client";
import { KnownErrors } from "@stackframe/stack-shared";
import { StackAssertionError, StatusError, captureError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import { generateUuid } from "@stackframe/stack-shared/dist/utils/uuids";
import * as yup from "yup";
import { fullPermissionInclude, isTeamSystemPermission, listServerPermissionDefinitions, serverPermissionDefinitionJsonFromDbType, serverPermissionDefinitionJsonFromTeamSystemDbType, teamPermissionIdSchema, teamSystemPermissionStringToDBType } from "./permissions";
import { decodeAccessToken } from "./tokens";
import { getServerUser } from "./users";
import { generateUuid } from "@stackframe/stack-shared/dist/utils/uuids";
import { SharedProvider, StandardProvider, sharedProviders, standardProviders } from "@stackframe/stack-shared/dist/interface/clientInterface";
import { OAuthProviderUpdateOptions, ProjectUpdateOptions } from "@/temporary-types";
import { StackAssertionError, StatusError, captureError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import { fullPermissionInclude, isTeamSystemPermission, listServerPermissionDefinitions, serverPermissionDefinitionJsonFromDbType, serverPermissionDefinitionJsonFromTeamSystemDbType, teamDBTypeToSystemPermissionString, teamPermissionIdSchema, teamSystemPermissionStringToDBType } from "./permissions";
import { KnownErrors } from "@stackframe/stack-shared";

function toDBSharedProvider(type: SharedProvider): ProxiedOAuthProviderType {
return ({
Expand All @@ -18,7 +16,7 @@ function toDBSharedProvider(type: SharedProvider): ProxiedOAuthProviderType {
"shared-facebook": "FACEBOOK",
"shared-microsoft": "MICROSOFT",
"shared-spotify": "SPOTIFY",
} as const)[type];
} as any)[type];
}

function toDBStandardProvider(type: StandardProvider): StandardOAuthProviderType {
Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.