mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
1001 lines
33 KiB
Plaintext
1001 lines
33 KiB
Plaintext
generator client {
|
|
provider = "prisma-client-js"
|
|
previewFeatures = ["tracing", "relationJoins"]
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("STACK_DATABASE_CONNECTION_STRING")
|
|
directUrl = env("STACK_DIRECT_DATABASE_CONNECTION_STRING")
|
|
}
|
|
|
|
model Project {
|
|
// Note that the project with ID `internal` is handled as a special case. All other project IDs are UUIDs.
|
|
id String @id
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
displayName String
|
|
description String @default("")
|
|
configId String @db.Uuid
|
|
config ProjectConfig @relation(fields: [configId], references: [id], onDelete: Cascade)
|
|
isProductionMode Boolean
|
|
userCount Int @default(0)
|
|
|
|
apiKeySets ApiKeySet[]
|
|
projectUsers ProjectUser[]
|
|
neonProvisionedProject NeonProvisionedProject?
|
|
tenancies Tenancy[]
|
|
verificationCodes VerificationCode[]
|
|
}
|
|
|
|
model ProjectConfig {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
allowLocalhost Boolean
|
|
signUpEnabled Boolean @default(true)
|
|
createTeamOnSignUp Boolean
|
|
clientTeamCreationEnabled Boolean
|
|
clientUserDeletionEnabled Boolean @default(false)
|
|
// TODO: remove this after moving everyone to project specific JWTs
|
|
legacyGlobalJwtSigning Boolean @default(false)
|
|
|
|
teamCreateDefaultSystemPermissions TeamSystemPermission[]
|
|
teamMemberDefaultSystemPermissions TeamSystemPermission[]
|
|
projects Project[]
|
|
oauthProviderConfigs OAuthProviderConfig[]
|
|
emailServiceConfig EmailServiceConfig?
|
|
domains ProjectDomain[]
|
|
permissions Permission[]
|
|
authMethodConfigs AuthMethodConfig[]
|
|
connectedAccountConfigs ConnectedAccountConfig[]
|
|
oauthAccountMergeStrategy OAuthAccountMergeStrategy @default(LINK_METHOD)
|
|
}
|
|
|
|
enum OAuthAccountMergeStrategy {
|
|
LINK_METHOD
|
|
RAISE_ERROR
|
|
ALLOW_DUPLICATES
|
|
}
|
|
|
|
model Tenancy {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
projectId String
|
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
|
|
branchId String
|
|
|
|
// If organizationId is NULL, hasNoOrganization must be TRUE. If organizationId is not NULL, hasNoOrganization must be NULL.
|
|
organizationId String? @db.Uuid
|
|
hasNoOrganization BooleanTrue?
|
|
|
|
teams Team[] @relation("TenancyTeams")
|
|
projectUsers ProjectUser[] @relation("TenancyProjectUsers")
|
|
authMethods AuthMethod[] @relation("TenancyAuthMethods")
|
|
contactChannels ContactChannel[] @relation("TenancyContactChannels")
|
|
connectedAccounts ConnectedAccount[] @relation("TenancyConnectedAccounts")
|
|
SentEmail SentEmail[]
|
|
cliAuthAttempts CliAuthAttempt[]
|
|
|
|
@@unique([projectId, branchId, organizationId])
|
|
@@unique([projectId, branchId, hasNoOrganization])
|
|
}
|
|
|
|
model ProjectDomain {
|
|
projectConfigId String @db.Uuid
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
domain String
|
|
handlerPath String
|
|
|
|
projectConfig ProjectConfig @relation(fields: [projectConfigId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([projectConfigId, domain])
|
|
}
|
|
|
|
model Team {
|
|
tenancyId String @db.Uuid
|
|
teamId String @default(uuid()) @db.Uuid
|
|
|
|
// Team IDs must be unique across all organizations (but not necessarily across all branches).
|
|
// To model this in the DB, we add two columns that are always equal to tenancy.projectId and tenancy.branchId.
|
|
mirroredProjectId String
|
|
mirroredBranchId String
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
displayName String
|
|
profileImageUrl String?
|
|
clientMetadata Json?
|
|
clientReadOnlyMetadata Json?
|
|
serverMetadata Json?
|
|
|
|
tenancy Tenancy @relation("TenancyTeams", fields: [tenancyId], references: [id], onDelete: Cascade)
|
|
permissions Permission[]
|
|
teamMembers TeamMember[]
|
|
|
|
@@id([tenancyId, teamId])
|
|
@@unique([mirroredProjectId, mirroredBranchId, teamId])
|
|
}
|
|
|
|
// This is used for fields that are boolean but only the true value is part of a unique constraint.
|
|
// For example if you want to allow only one selected team per user, you can make an optional field with this type and add a unique constraint.
|
|
// Only the true value is considered for the unique constraint, the null value is not.
|
|
enum BooleanTrue {
|
|
TRUE
|
|
}
|
|
|
|
model TeamMember {
|
|
tenancyId String @db.Uuid
|
|
projectUserId String @db.Uuid
|
|
teamId String @db.Uuid
|
|
|
|
// This will override the displayName of the user in this team.
|
|
displayName String?
|
|
// This will override the profileImageUrl of the user in this team.
|
|
profileImageUrl String?
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
projectUser ProjectUser @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
|
|
team Team @relation(fields: [tenancyId, teamId], references: [tenancyId, teamId], onDelete: Cascade)
|
|
isSelected BooleanTrue?
|
|
|
|
directPermissions TeamMemberDirectPermission[]
|
|
|
|
@@id([tenancyId, projectUserId, teamId])
|
|
@@unique([tenancyId, projectUserId, isSelected])
|
|
}
|
|
|
|
model ProjectUserDirectPermission {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
tenancyId String @db.Uuid
|
|
projectUserId String @db.Uuid
|
|
permissionDbId String? @db.Uuid
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
projectUser ProjectUser @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
|
|
|
|
// no [systemPermission] yet, we'll add it when the need arises
|
|
permission Permission? @relation(fields: [permissionDbId], references: [dbId], onDelete: Cascade)
|
|
|
|
@@unique([tenancyId, projectUserId, permissionDbId])
|
|
}
|
|
|
|
model TeamMemberDirectPermission {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
tenancyId String @db.Uuid
|
|
projectUserId String @db.Uuid
|
|
teamId String @db.Uuid
|
|
permissionDbId String? @db.Uuid
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
teamMember TeamMember @relation(fields: [tenancyId, projectUserId, teamId], references: [tenancyId, projectUserId, teamId], onDelete: Cascade)
|
|
|
|
// exactly one of [permissionId && permission] or [systemPermission] must be set
|
|
permission Permission? @relation(fields: [permissionDbId], references: [dbId], onDelete: Cascade)
|
|
systemPermission TeamSystemPermission?
|
|
|
|
@@unique([tenancyId, projectUserId, teamId, permissionDbId])
|
|
@@unique([tenancyId, projectUserId, teamId, systemPermission])
|
|
}
|
|
|
|
model Permission {
|
|
// The ID of this permission, as is chosen by and exposed to the user. It is different from the database ID, which is randomly generated and only used internally.
|
|
queryableId String
|
|
// The database ID of this permission. This is never exposed to any client and is only used to make sure the database has an ID column.
|
|
dbId String @id @default(uuid()) @db.Uuid
|
|
// exactly one of [projectConfigId && projectConfig] or [projectId && teamId && team] must be set
|
|
projectConfigId String? @db.Uuid
|
|
tenancyId String? @db.Uuid
|
|
teamId String? @db.Uuid
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
description String?
|
|
|
|
// The scope of the permission. If projectConfigId is set, may be USER or TEAM; if teamId is set, must be TEAM.
|
|
scope PermissionScope
|
|
|
|
projectConfig ProjectConfig? @relation(fields: [projectConfigId], references: [id], onDelete: Cascade)
|
|
team Team? @relation(fields: [tenancyId, teamId], references: [tenancyId, teamId], onDelete: Cascade)
|
|
|
|
parentEdges PermissionEdge[] @relation("ChildPermission")
|
|
childEdges PermissionEdge[] @relation("ParentPermission")
|
|
teamMemberDirectPermission TeamMemberDirectPermission[]
|
|
projectUserDirectPermission ProjectUserDirectPermission[]
|
|
|
|
isDefaultTeamCreatorPermission Boolean @default(false)
|
|
isDefaultTeamMemberPermission Boolean @default(false)
|
|
isDefaultProjectPermission Boolean @default(false)
|
|
|
|
@@unique([projectConfigId, queryableId])
|
|
@@unique([tenancyId, teamId, queryableId])
|
|
}
|
|
|
|
enum PermissionScope {
|
|
PROJECT
|
|
TEAM
|
|
}
|
|
|
|
enum TeamSystemPermission {
|
|
UPDATE_TEAM
|
|
DELETE_TEAM
|
|
READ_MEMBERS
|
|
REMOVE_MEMBERS
|
|
INVITE_MEMBERS
|
|
}
|
|
|
|
model PermissionEdge {
|
|
edgeId String @id @default(uuid()) @db.Uuid
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// exactly one of [parentPermissionDbId && parentPermission] or [parentTeamSystemPermission] must be set
|
|
parentPermissionDbId String? @db.Uuid
|
|
parentPermission Permission? @relation("ParentPermission", fields: [parentPermissionDbId], references: [dbId], onDelete: Cascade)
|
|
parentTeamSystemPermission TeamSystemPermission?
|
|
|
|
childPermissionDbId String @db.Uuid
|
|
childPermission Permission @relation("ChildPermission", fields: [childPermissionDbId], references: [dbId], onDelete: Cascade)
|
|
|
|
@@index([parentPermissionDbId])
|
|
@@index([childPermissionDbId])
|
|
}
|
|
|
|
model ProjectUser {
|
|
tenancyId String @db.Uuid
|
|
projectUserId String @default(uuid()) @db.Uuid
|
|
|
|
// User IDs must be unique across all organizations (but not necessarily across all branches).
|
|
// To model this in the DB, we add two columns that are always equal to tenancy.projectId and tenancy.branchId.
|
|
mirroredProjectId String
|
|
mirroredBranchId String
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
profileImageUrl String?
|
|
displayName String?
|
|
serverMetadata Json?
|
|
clientReadOnlyMetadata Json?
|
|
clientMetadata Json?
|
|
requiresTotpMfa Boolean @default(false)
|
|
totpSecret Bytes?
|
|
isAnonymous Boolean @default(false)
|
|
|
|
tenancy Tenancy @relation("TenancyProjectUsers", fields: [tenancyId], references: [id], onDelete: Cascade)
|
|
project Project @relation(fields: [mirroredProjectId], references: [id], onDelete: Cascade)
|
|
projectUserRefreshTokens ProjectUserRefreshToken[]
|
|
projectUserAuthorizationCodes ProjectUserAuthorizationCode[]
|
|
projectUserOAuthAccounts ProjectUserOAuthAccount[]
|
|
teamMembers TeamMember[]
|
|
contactChannels ContactChannel[]
|
|
authMethods AuthMethod[]
|
|
connectedAccounts ConnectedAccount[]
|
|
|
|
// some backlinks for the unique constraints on some auth methods
|
|
passwordAuthMethod PasswordAuthMethod[]
|
|
passkeyAuthMethod PasskeyAuthMethod[]
|
|
otpAuthMethod OtpAuthMethod[]
|
|
oauthAuthMethod OAuthAuthMethod[]
|
|
SentEmail SentEmail[]
|
|
directPermissions ProjectUserDirectPermission[]
|
|
|
|
@@id([tenancyId, projectUserId])
|
|
@@unique([mirroredProjectId, mirroredBranchId, projectUserId])
|
|
// indices for sorting and filtering
|
|
@@index([tenancyId, displayName(sort: Asc)], name: "ProjectUser_displayName_asc")
|
|
@@index([tenancyId, displayName(sort: Desc)], name: "ProjectUser_displayName_desc")
|
|
@@index([tenancyId, createdAt(sort: Asc)], name: "ProjectUser_createdAt_asc")
|
|
@@index([tenancyId, createdAt(sort: Desc)], name: "ProjectUser_createdAt_desc")
|
|
}
|
|
|
|
// This should be renamed to "OAuthAccount" as it is not always bound to a user
|
|
// When ever a user goes through the OAuth flow and gets an account ID from the OAuth provider, we store that here.
|
|
model ProjectUserOAuthAccount {
|
|
tenancyId String @db.Uuid
|
|
projectUserId String @db.Uuid
|
|
projectConfigId String @db.Uuid
|
|
oauthProviderConfigId String
|
|
providerAccountId String
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// This is used for the user to distinguish between multiple accounts from the same provider.
|
|
// we might want to add more user info here later
|
|
email String?
|
|
|
|
providerConfig OAuthProviderConfig @relation(fields: [projectConfigId, oauthProviderConfigId], references: [projectConfigId, id], onDelete: Cascade)
|
|
// Before the OAuth account is connected to a use (for example, in the link oauth process), the projectUser is null.
|
|
projectUser ProjectUser? @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
|
|
oauthTokens OAuthToken[]
|
|
oauthAccessToken OAuthAccessToken[]
|
|
|
|
// At lease one of the authMethod or connectedAccount should be set.
|
|
connectedAccount ConnectedAccount?
|
|
oauthAuthMethod OAuthAuthMethod?
|
|
|
|
@@id([tenancyId, oauthProviderConfigId, providerAccountId])
|
|
@@index([tenancyId, projectUserId])
|
|
}
|
|
|
|
enum ContactChannelType {
|
|
EMAIL
|
|
// PHONE
|
|
}
|
|
|
|
model ContactChannel {
|
|
tenancyId String @db.Uuid
|
|
projectUserId String @db.Uuid
|
|
id String @default(uuid()) @db.Uuid
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
type ContactChannelType
|
|
isPrimary BooleanTrue?
|
|
usedForAuth BooleanTrue?
|
|
isVerified Boolean
|
|
value String
|
|
|
|
tenancy Tenancy @relation("TenancyContactChannels", fields: [tenancyId], references: [id], onDelete: Cascade)
|
|
projectUser ProjectUser @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
|
|
|
|
@@id([tenancyId, projectUserId, id])
|
|
// each user has at most one primary contact channel of each type
|
|
@@unique([tenancyId, projectUserId, type, isPrimary])
|
|
// value must be unique per user per type
|
|
@@unique([tenancyId, projectUserId, type, value])
|
|
// only one contact channel per project with the same value and type can be used for auth
|
|
@@unique([tenancyId, type, value, usedForAuth])
|
|
}
|
|
|
|
model ConnectedAccountConfig {
|
|
projectConfigId String @db.Uuid
|
|
id String @default(uuid()) @db.Uuid
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
enabled Boolean @default(true)
|
|
|
|
// this should never be null
|
|
oauthProviderConfig OAuthProviderConfig?
|
|
projectConfig ProjectConfig @relation(fields: [projectConfigId], references: [id], onDelete: Cascade)
|
|
connectedAccounts ConnectedAccount[]
|
|
|
|
@@id([projectConfigId, id])
|
|
}
|
|
|
|
model ConnectedAccount {
|
|
tenancyId String @db.Uuid
|
|
id String @default(uuid()) @db.Uuid
|
|
projectConfigId String @db.Uuid
|
|
connectedAccountConfigId String @db.Uuid
|
|
projectUserId String @db.Uuid
|
|
oauthProviderConfigId String
|
|
providerAccountId String
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
oauthAccount ProjectUserOAuthAccount @relation(fields: [tenancyId, oauthProviderConfigId, providerAccountId], references: [tenancyId, oauthProviderConfigId, providerAccountId])
|
|
tenancy Tenancy @relation("TenancyConnectedAccounts", fields: [tenancyId], references: [id], onDelete: Cascade)
|
|
projectUser ProjectUser @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
|
|
connectedAccountConfig ConnectedAccountConfig @relation(fields: [projectConfigId, connectedAccountConfigId], references: [projectConfigId, id], onDelete: Cascade)
|
|
oauthProviderConfig OAuthProviderConfig @relation(fields: [projectConfigId, oauthProviderConfigId], references: [projectConfigId, id], onDelete: Cascade)
|
|
|
|
@@id([tenancyId, id])
|
|
@@unique([tenancyId, oauthProviderConfigId, providerAccountId])
|
|
}
|
|
|
|
model AuthMethodConfig {
|
|
projectConfigId String @db.Uuid
|
|
id String @default(uuid()) @db.Uuid
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
enabled Boolean @default(true)
|
|
|
|
// exactly one of xyzConfig should be set
|
|
otpConfig OtpAuthMethodConfig?
|
|
oauthProviderConfig OAuthProviderConfig?
|
|
passwordConfig PasswordAuthMethodConfig?
|
|
passkeyConfig PasskeyAuthMethodConfig?
|
|
|
|
projectConfig ProjectConfig @relation(fields: [projectConfigId], references: [id], onDelete: Cascade)
|
|
authMethods AuthMethod[]
|
|
|
|
@@id([projectConfigId, id])
|
|
}
|
|
|
|
model OtpAuthMethodConfig {
|
|
projectConfigId String @db.Uuid
|
|
authMethodConfigId String @db.Uuid
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
contactChannelType ContactChannelType
|
|
|
|
authMethodConfig AuthMethodConfig @relation(fields: [projectConfigId, authMethodConfigId], references: [projectConfigId, id], onDelete: Cascade)
|
|
|
|
@@id([projectConfigId, authMethodConfigId])
|
|
}
|
|
|
|
model PasswordAuthMethodConfig {
|
|
projectConfigId String @db.Uuid
|
|
authMethodConfigId String @db.Uuid
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
authMethodConfig AuthMethodConfig @relation(fields: [projectConfigId, authMethodConfigId], references: [projectConfigId, id], onDelete: Cascade)
|
|
|
|
@@id([projectConfigId, authMethodConfigId])
|
|
}
|
|
|
|
model PasskeyAuthMethodConfig {
|
|
projectConfigId String @db.Uuid
|
|
authMethodConfigId String @db.Uuid
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
authMethodConfig AuthMethodConfig @relation(fields: [projectConfigId, authMethodConfigId], references: [projectConfigId, id], onDelete: Cascade)
|
|
|
|
@@id([projectConfigId, authMethodConfigId])
|
|
}
|
|
|
|
// Both the connected account and auth methods can use this configuration.
|
|
model OAuthProviderConfig {
|
|
projectConfigId String @db.Uuid
|
|
id String
|
|
authMethodConfigId String? @db.Uuid
|
|
connectedAccountConfigId String? @db.Uuid
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Exactly one of the xyzOAuthConfig variables should be set.
|
|
proxiedOAuthConfig ProxiedOAuthProviderConfig?
|
|
standardOAuthConfig StandardOAuthProviderConfig?
|
|
|
|
// At least one of authMethodConfig or connectedAccountConfig should be set.
|
|
// The config relation will never be unset, but the enabled field might be set to false on authMethodConfig or connectedAccountConfig.
|
|
authMethodConfig AuthMethodConfig? @relation(fields: [projectConfigId, authMethodConfigId], references: [projectConfigId, id], onDelete: Cascade)
|
|
connectedAccountConfig ConnectedAccountConfig? @relation(fields: [projectConfigId, connectedAccountConfigId], references: [projectConfigId, id], onDelete: Cascade)
|
|
|
|
projectConfig ProjectConfig @relation(fields: [projectConfigId], references: [id], onDelete: Cascade)
|
|
projectUserOAuthAccounts ProjectUserOAuthAccount[]
|
|
connectedAccounts ConnectedAccount[]
|
|
oauthAuthMethod OAuthAuthMethod[]
|
|
|
|
@@id([projectConfigId, id])
|
|
@@unique([projectConfigId, authMethodConfigId])
|
|
@@unique([projectConfigId, connectedAccountConfigId])
|
|
}
|
|
|
|
model ProxiedOAuthProviderConfig {
|
|
projectConfigId String @db.Uuid
|
|
id String
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
type ProxiedOAuthProviderType
|
|
|
|
providerConfig OAuthProviderConfig @relation(fields: [projectConfigId, id], references: [projectConfigId, id], onDelete: Cascade)
|
|
|
|
@@id([projectConfigId, id])
|
|
// each type of proxied OAuth provider can only be used once per project
|
|
@@unique([projectConfigId, type])
|
|
}
|
|
|
|
enum ProxiedOAuthProviderType {
|
|
GITHUB
|
|
GOOGLE
|
|
MICROSOFT
|
|
SPOTIFY
|
|
}
|
|
|
|
model StandardOAuthProviderConfig {
|
|
projectConfigId String @db.Uuid
|
|
id String
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
type StandardOAuthProviderType
|
|
clientId String
|
|
clientSecret String
|
|
|
|
// optional extra parameters for specific oauth providers
|
|
// Facebook business integration requires a config_id
|
|
facebookConfigId String?
|
|
// Microsoft organizational directory (tenant)
|
|
microsoftTenantId String?
|
|
|
|
providerConfig OAuthProviderConfig @relation(fields: [projectConfigId, id], references: [projectConfigId, id], onDelete: Cascade)
|
|
|
|
// each type of standard OAuth provider can only be used once per project
|
|
@@id([projectConfigId, id])
|
|
}
|
|
|
|
model AuthMethod {
|
|
tenancyId String @db.Uuid
|
|
id String @default(uuid()) @db.Uuid
|
|
projectUserId String @db.Uuid
|
|
authMethodConfigId String @db.Uuid
|
|
projectConfigId String @db.Uuid
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// exactly one of the xyzAuthMethods should be set
|
|
otpAuthMethod OtpAuthMethod?
|
|
passwordAuthMethod PasswordAuthMethod?
|
|
passkeyAuthMethod PasskeyAuthMethod?
|
|
oauthAuthMethod OAuthAuthMethod?
|
|
|
|
tenancy Tenancy @relation("TenancyAuthMethods", fields: [tenancyId], references: [id], onDelete: Cascade)
|
|
projectUser ProjectUser @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
|
|
authMethodConfig AuthMethodConfig @relation(fields: [projectConfigId, authMethodConfigId], references: [projectConfigId, id], onDelete: Cascade)
|
|
|
|
@@id([tenancyId, id])
|
|
@@index([tenancyId, projectUserId])
|
|
}
|
|
|
|
model OtpAuthMethod {
|
|
tenancyId String @db.Uuid
|
|
authMethodId String @db.Uuid
|
|
projectUserId String @db.Uuid
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
authMethod AuthMethod @relation(fields: [tenancyId, authMethodId], references: [tenancyId, id], onDelete: Cascade)
|
|
projectUser ProjectUser @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
|
|
|
|
@@id([tenancyId, authMethodId])
|
|
// a user can only have one OTP auth method
|
|
@@unique([tenancyId, projectUserId])
|
|
}
|
|
|
|
model PasswordAuthMethod {
|
|
tenancyId String @db.Uuid
|
|
authMethodId String @db.Uuid
|
|
projectUserId String @db.Uuid
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
passwordHash String
|
|
|
|
authMethod AuthMethod @relation(fields: [tenancyId, authMethodId], references: [tenancyId, id], onDelete: Cascade)
|
|
projectUser ProjectUser @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
|
|
|
|
@@id([tenancyId, authMethodId])
|
|
// a user can only have one password auth method
|
|
@@unique([tenancyId, projectUserId])
|
|
}
|
|
|
|
model PasskeyAuthMethod {
|
|
tenancyId String @db.Uuid
|
|
authMethodId String @db.Uuid
|
|
projectUserId String @db.Uuid
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
credentialId String
|
|
publicKey String
|
|
userHandle String
|
|
transports String[]
|
|
credentialDeviceType String
|
|
counter Int
|
|
|
|
authMethod AuthMethod @relation(fields: [tenancyId, authMethodId], references: [tenancyId, id], onDelete: Cascade)
|
|
projectUser ProjectUser @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
|
|
|
|
@@id([tenancyId, authMethodId])
|
|
// a user can only have one password auth method
|
|
@@unique([tenancyId, projectUserId])
|
|
}
|
|
|
|
// This connects to projectUserOauthAccount, which might be shared between auth method and connected account.
|
|
model OAuthAuthMethod {
|
|
tenancyId String @db.Uuid
|
|
projectConfigId String @db.Uuid
|
|
authMethodId String @db.Uuid
|
|
oauthProviderConfigId String
|
|
providerAccountId String
|
|
projectUserId String @db.Uuid
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
authMethod AuthMethod @relation(fields: [tenancyId, authMethodId], references: [tenancyId, id], onDelete: Cascade)
|
|
oauthAccount ProjectUserOAuthAccount @relation(fields: [tenancyId, oauthProviderConfigId, providerAccountId], references: [tenancyId, oauthProviderConfigId, providerAccountId])
|
|
projectUser ProjectUser @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
|
|
oauthProviderConfig OAuthProviderConfig @relation(fields: [projectConfigId, oauthProviderConfigId], references: [projectConfigId, id], onDelete: Cascade)
|
|
|
|
@@id([tenancyId, authMethodId])
|
|
@@unique([tenancyId, oauthProviderConfigId, providerAccountId])
|
|
}
|
|
|
|
enum StandardOAuthProviderType {
|
|
GITHUB
|
|
FACEBOOK
|
|
GOOGLE
|
|
MICROSOFT
|
|
SPOTIFY
|
|
DISCORD
|
|
GITLAB
|
|
BITBUCKET
|
|
LINKEDIN
|
|
APPLE
|
|
X
|
|
}
|
|
|
|
model OAuthToken {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
|
|
tenancyId String @db.Uuid
|
|
oAuthProviderConfigId String
|
|
providerAccountId String
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
projectUserOAuthAccount ProjectUserOAuthAccount @relation(fields: [tenancyId, oAuthProviderConfigId, providerAccountId], references: [tenancyId, oauthProviderConfigId, providerAccountId], onDelete: Cascade)
|
|
|
|
refreshToken String
|
|
scopes String[]
|
|
}
|
|
|
|
model OAuthAccessToken {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
|
|
tenancyId String @db.Uuid
|
|
oAuthProviderConfigId String
|
|
providerAccountId String
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
projectUserOAuthAccount ProjectUserOAuthAccount @relation(fields: [tenancyId, oAuthProviderConfigId, providerAccountId], references: [tenancyId, oauthProviderConfigId, providerAccountId], onDelete: Cascade)
|
|
|
|
accessToken String
|
|
scopes String[]
|
|
expiresAt DateTime
|
|
}
|
|
|
|
model OAuthOuterInfo {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
|
|
info Json
|
|
innerState String @unique
|
|
expiresAt DateTime
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
model ProjectUserRefreshToken {
|
|
id String @default(uuid()) @db.Uuid
|
|
tenancyId String @db.Uuid
|
|
projectUserId String @db.Uuid
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
refreshToken String @unique
|
|
expiresAt DateTime?
|
|
isImpersonation Boolean @default(false)
|
|
|
|
projectUser ProjectUser @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
|
|
|
|
@@id([tenancyId, id])
|
|
}
|
|
|
|
model ProjectUserAuthorizationCode {
|
|
tenancyId String @db.Uuid
|
|
projectUserId String @db.Uuid
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
authorizationCode String @unique
|
|
redirectUri String
|
|
expiresAt DateTime
|
|
|
|
codeChallenge String
|
|
codeChallengeMethod String
|
|
|
|
newUser Boolean
|
|
afterCallbackRedirectUrl String?
|
|
|
|
projectUser ProjectUser @relation(fields: [tenancyId, projectUserId], references: [tenancyId, projectUserId], onDelete: Cascade)
|
|
|
|
@@id([tenancyId, authorizationCode])
|
|
}
|
|
|
|
model VerificationCode {
|
|
projectId String
|
|
branchId String
|
|
id String @default(uuid()) @db.Uuid
|
|
|
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
type VerificationCodeType
|
|
code String
|
|
expiresAt DateTime
|
|
usedAt DateTime?
|
|
redirectUrl String?
|
|
method Json @default("null")
|
|
data Json
|
|
attemptCount Int @default(0)
|
|
|
|
@@id([projectId, branchId, id])
|
|
@@unique([projectId, branchId, code])
|
|
@@index([data(ops: JsonbPathOps)], type: Gin)
|
|
}
|
|
|
|
enum VerificationCodeType {
|
|
ONE_TIME_PASSWORD
|
|
PASSWORD_RESET
|
|
CONTACT_CHANNEL_VERIFICATION
|
|
TEAM_INVITATION
|
|
MFA_ATTEMPT
|
|
PASSKEY_REGISTRATION_CHALLENGE
|
|
PASSKEY_AUTHENTICATION_CHALLENGE
|
|
NEON_INTEGRATION_PROJECT_TRANSFER
|
|
}
|
|
|
|
//#region API keys
|
|
|
|
model ApiKeySet {
|
|
projectId String
|
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
id String @default(uuid()) @db.Uuid
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
description String
|
|
expiresAt DateTime
|
|
manuallyRevokedAt DateTime?
|
|
publishableClientKey String? @unique
|
|
secretServerKey String? @unique
|
|
superSecretAdminKey String? @unique
|
|
|
|
@@id([projectId, id])
|
|
}
|
|
|
|
model EmailServiceConfig {
|
|
projectConfigId String @id @db.Uuid
|
|
projectConfig ProjectConfig @relation(fields: [projectConfigId], references: [id], onDelete: Cascade)
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
proxiedEmailServiceConfig ProxiedEmailServiceConfig?
|
|
standardEmailServiceConfig StandardEmailServiceConfig?
|
|
|
|
emailTemplates EmailTemplate[]
|
|
}
|
|
|
|
enum EmailTemplateType {
|
|
EMAIL_VERIFICATION
|
|
PASSWORD_RESET
|
|
MAGIC_LINK
|
|
TEAM_INVITATION
|
|
}
|
|
|
|
model EmailTemplate {
|
|
projectConfigId String @db.Uuid
|
|
emailServiceConfig EmailServiceConfig @relation(fields: [projectConfigId], references: [projectConfigId], onDelete: Cascade)
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
content Json
|
|
type EmailTemplateType
|
|
subject String
|
|
|
|
@@id([projectConfigId, type])
|
|
}
|
|
|
|
model ProxiedEmailServiceConfig {
|
|
projectConfigId String @id @db.Uuid
|
|
emailServiceConfig EmailServiceConfig @relation(fields: [projectConfigId], references: [projectConfigId], onDelete: Cascade)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
model StandardEmailServiceConfig {
|
|
projectConfigId String @id @db.Uuid
|
|
emailServiceConfig EmailServiceConfig @relation(fields: [projectConfigId], references: [projectConfigId], onDelete: Cascade)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
senderName String
|
|
senderEmail String
|
|
host String
|
|
port Int
|
|
username String
|
|
password String
|
|
}
|
|
|
|
//#endregion
|
|
|
|
//#region IdP
|
|
model IdPAccountToCdfcResultMapping {
|
|
idpId String
|
|
id String
|
|
|
|
idpAccountId String @unique @db.Uuid
|
|
cdfcResult Json
|
|
|
|
@@id([idpId, id])
|
|
}
|
|
|
|
model ProjectWrapperCodes {
|
|
idpId String
|
|
id String @default(uuid()) @db.Uuid
|
|
|
|
interactionUid String
|
|
authorizationCode String @unique
|
|
|
|
cdfcResult Json
|
|
|
|
@@id([idpId, id])
|
|
}
|
|
|
|
model IdPAdapterData {
|
|
idpId String
|
|
model String
|
|
id String
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
payload Json
|
|
expiresAt DateTime
|
|
|
|
@@id([idpId, model, id])
|
|
@@index([payload(ops: JsonbPathOps)], type: Gin)
|
|
@@index([expiresAt])
|
|
}
|
|
|
|
//#endregion
|
|
|
|
//#region Neon integration
|
|
model NeonProvisionedProject {
|
|
projectId String @id
|
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
neonClientId String
|
|
}
|
|
|
|
//#endregion
|
|
|
|
//#region Events
|
|
|
|
model Event {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// if isWide == false, then eventEndedAt is always equal to eventStartedAt
|
|
isWide Boolean
|
|
eventStartedAt DateTime
|
|
eventEndedAt DateTime
|
|
|
|
// TODO: add event_type, and at least one of either system_event_type or event_type is always set
|
|
systemEventTypeIds String[]
|
|
data Json
|
|
|
|
// ============================== BEGIN END USER PROPERTIES ==============================
|
|
// Below are properties describing the end user that caused this event to be logged
|
|
// This is different from a request IP. See: apps/backend/src/lib/end-users.tsx
|
|
|
|
// Note that the IP may have been spoofed, unless isEndUserIpInfoGuessTrusted is true
|
|
endUserIpInfoGuessId String? @db.Uuid
|
|
endUserIpInfoGuess EventIpInfo? @relation("EventIpInfo", fields: [endUserIpInfoGuessId], references: [id])
|
|
// If true, then endUserIpInfoGuess is not spoofed (might still be behind VPNs/proxies). If false, then the values may be spoofed.
|
|
isEndUserIpInfoGuessTrusted Boolean @default(false)
|
|
// =============================== END END USER PROPERTIES ===============================
|
|
|
|
@@index([data(ops: JsonbPathOps)], type: Gin)
|
|
}
|
|
|
|
// An IP address that was seen in an event. Use the location fields instead of refetching the location from the ip, as the real-world geoip data may have changed since the event was logged.
|
|
model EventIpInfo {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
|
|
ip String
|
|
|
|
countryCode String?
|
|
regionCode String?
|
|
cityName String?
|
|
latitude Float?
|
|
longitude Float?
|
|
tzIdentifier String?
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
events Event[] @relation("EventIpInfo")
|
|
}
|
|
|
|
//#endregion
|
|
|
|
model SentEmail {
|
|
tenancyId String @db.Uuid
|
|
|
|
id String @default(uuid()) @db.Uuid
|
|
|
|
userId String? @db.Uuid
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
senderConfig Json
|
|
to String[]
|
|
subject String
|
|
html String?
|
|
text String?
|
|
|
|
error Json?
|
|
tenancy Tenancy @relation(fields: [tenancyId], references: [id], onDelete: Cascade)
|
|
user ProjectUser? @relation(fields: [tenancyId, userId], references: [tenancyId, projectUserId], onDelete: Cascade)
|
|
|
|
@@id([tenancyId, id])
|
|
}
|
|
|
|
model CliAuthAttempt {
|
|
tenancyId String @db.Uuid
|
|
tenancy Tenancy @relation(fields: [tenancyId], references: [id], onDelete: Cascade)
|
|
|
|
id String @default(uuid()) @db.Uuid
|
|
pollingCode String @unique
|
|
loginCode String @unique
|
|
refreshToken String?
|
|
expiresAt DateTime
|
|
usedAt DateTime?
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@id([tenancyId, id])
|
|
}
|