This repository has been archived by the owner on Sep 17, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Create the
auth_provider
module
- Loading branch information
1 parent
4411d94
commit efc852f
Showing
16 changed files
with
677 additions
and
0 deletions.
There are no files selected for viewing
22 changes: 22 additions & 0 deletions
22
modules/identities/db/migrations/20240703025745_/migration.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
-- CreateTable | ||
CREATE TABLE "UserIdentities" ( | ||
"userId" UUID NOT NULL, | ||
"identityType" TEXT NOT NULL, | ||
"identityId" TEXT NOT NULL, | ||
"uniqueData" JSONB NOT NULL, | ||
"additionalData" JSONB NOT NULL, | ||
|
||
CONSTRAINT "UserIdentities_pkey" PRIMARY KEY ("userId","identityType","identityId") | ||
); | ||
|
||
-- CreateIndex | ||
CREATE INDEX "UserIdentities_userId_idx" ON "UserIdentities"("userId"); | ||
|
||
-- CreateIndex | ||
CREATE INDEX "UserIdentities_identityType_identityId_idx" ON "UserIdentities"("identityType", "identityId"); | ||
|
||
-- CreateIndex | ||
CREATE INDEX "UserIdentities_identityType_identityId_uniqueData_idx" ON "UserIdentities"("identityType", "identityId", "uniqueData"); | ||
|
||
-- CreateIndex | ||
CREATE UNIQUE INDEX "UserIdentities_identityType_identityId_uniqueData_key" ON "UserIdentities"("identityType", "identityId", "uniqueData"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Please do not edit this file manually | ||
# It should be added in your version-control system (i.e. Git) | ||
provider = "postgresql" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// Do not modify this `datasource` block | ||
datasource db { | ||
provider = "postgresql" | ||
url = env("DATABASE_URL") | ||
} | ||
|
||
model UserIdentities { | ||
userId String @db.Uuid | ||
// Used to identify the user from the identity | ||
identityType String | ||
identityId String | ||
uniqueData Json | ||
additionalData Json | ||
// Additional indexes for speed | ||
@@index([userId]) | ||
@@index([identityType, identityId]) | ||
// Each user should only have one identity per (type, id) pair (for now) | ||
@@id([userId, identityType, identityId]) | ||
// Each identity should only be linked to one user | ||
@@index([identityType, identityId, uniqueData]) | ||
@@unique([identityType, identityId, uniqueData]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
{ | ||
"name": "Identites", | ||
"description": "Manage identities and identity data for users.", | ||
"icon": "key", | ||
"tags": [ | ||
"core", | ||
"user", | ||
"auth" | ||
], | ||
"authors": [ | ||
"rivet-gg", | ||
"Blckbrry-Pi" | ||
], | ||
"status": "beta", | ||
"dependencies": { | ||
"rate_limit": {}, | ||
"users": {}, | ||
"tokens": {} | ||
}, | ||
"scripts": { | ||
"list": { | ||
"name": "List Identities", | ||
"description": "List all identities the user is associated with.", | ||
"public": true | ||
}, | ||
"get": { | ||
"name": "Get Identity Data", | ||
"description": "Get the data associated with a specific identity for a user." | ||
}, | ||
"set": { | ||
"name": "Set Identity Data", | ||
"description": "Set the data associated with a specific identity for a user." | ||
}, | ||
|
||
"sign_in": { | ||
"name": "Sign In With Identity", | ||
"description": "Sign in to a user with an identity." | ||
}, | ||
"sign_up": { | ||
"name": "Sign Up With Identity", | ||
"description": "Sign up with an identity. Creates a new user." | ||
}, | ||
"sign_in_or_sign_up": { | ||
"name": "Sign In or Sign Up With Identity", | ||
"description": "Sign in to a user with an identity, creating a new user if it fails." | ||
}, | ||
"link": { | ||
"name": "Link Identity To User", | ||
"description": "Link a new identity and its associated data to a user. This is used for login and non-login identites." | ||
} | ||
}, | ||
"routes": {}, | ||
"errors": { | ||
"identity_provider_not_found": { | ||
"name": "Identity Provider Not Found" | ||
}, | ||
"identity_provider_already_added": { | ||
"name": "Identity Provider Already Added To User" | ||
}, | ||
"identity_provider_already_used": { | ||
"name": "Identity Provider Already Used By Other User" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { ScriptContext } from "../module.gen.ts"; | ||
import { IdentityData, IdentityProviderInfo } from "../utils/types.ts"; | ||
|
||
export interface Request { | ||
userToken: string; | ||
info: IdentityProviderInfo; | ||
} | ||
|
||
export interface Response { | ||
data: { | ||
uniqueData: IdentityData; | ||
additionalData: IdentityData; | ||
} | null; | ||
} | ||
|
||
export async function run( | ||
ctx: ScriptContext, | ||
req: Request, | ||
): Promise<Response> { | ||
await ctx.modules.rateLimit.throttle({ | ||
key: req.userToken, | ||
period: 10, | ||
requests: 10, | ||
type: "user", | ||
}); | ||
|
||
// Ensure the user token is valid and get the user ID | ||
const { userId } = await ctx.modules.users.authenticateToken({ userToken: req.userToken } ); | ||
|
||
// Get identity data | ||
const identity = await ctx.db.userIdentities.findFirst({ | ||
where: { | ||
userId, | ||
identityType: req.info.identityType, | ||
identityId: req.info.identityId, | ||
}, | ||
select: { | ||
uniqueData: true, | ||
additionalData: true | ||
} | ||
}); | ||
|
||
// Type checking to make typescript happy | ||
const data = identity ?? null; | ||
if (!data) { | ||
return { data: null }; | ||
} | ||
|
||
const { uniqueData, additionalData } = data; | ||
if (typeof uniqueData !== 'object' || Array.isArray(uniqueData) || uniqueData === null) { | ||
return { data: null }; | ||
} | ||
if (typeof additionalData !== 'object' || Array.isArray(additionalData) || additionalData === null) { | ||
return { data: null }; | ||
} | ||
|
||
return { data: { uniqueData, additionalData } }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { RuntimeError, ScriptContext } from "../module.gen.ts"; | ||
import { IdentityDataInput, IdentityProviderInfo } from "../utils/types.ts"; | ||
|
||
export interface Request { | ||
userToken: string; | ||
info: IdentityProviderInfo; | ||
uniqueData: IdentityDataInput; | ||
additionalData: IdentityDataInput; | ||
} | ||
|
||
export interface Response { | ||
identityProviders: IdentityProviderInfo[]; | ||
} | ||
|
||
export async function run( | ||
ctx: ScriptContext, | ||
req: Request, | ||
): Promise<Response> { | ||
await ctx.modules.rateLimit.throttle({ | ||
key: req.userToken, | ||
period: 10, | ||
requests: 10, | ||
type: "user", | ||
}); | ||
|
||
// Ensure the user token is valid and get the user ID | ||
const { userId } = await ctx.modules.users.authenticateToken({ userToken: req.userToken } ); | ||
|
||
// Error if this identity provider is ALREADY associated with the user | ||
const { data: prevData } = await ctx.modules.identities.get({ userToken: req.userToken, info: req.info }); | ||
if (prevData) throw new RuntimeError("identity_provider_already_added"); | ||
|
||
// Add a new entry to the table with the associated data | ||
await ctx.db.userIdentities.create({ | ||
data: { | ||
userId, | ||
identityType: req.info.identityType, | ||
identityId: req.info.identityId, | ||
uniqueData: req.uniqueData, | ||
additionalData: req.additionalData, | ||
}, | ||
}); | ||
|
||
return await ctx.modules.identities.list({ userToken: req.userToken }); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { ScriptContext } from "../module.gen.ts"; | ||
import { IdentityProviderInfo } from "../utils/types.ts"; | ||
|
||
export interface Request { | ||
userToken: string; | ||
} | ||
|
||
export interface Response { | ||
identityProviders: IdentityProviderInfo[]; | ||
} | ||
|
||
export async function run( | ||
ctx: ScriptContext, | ||
req: Request, | ||
): Promise<Response> { | ||
await ctx.modules.rateLimit.throttlePublic({}); | ||
|
||
// Ensure the user token is valid and get the user ID | ||
const { userId } = await ctx.modules.users.authenticateToken({ userToken: req.userToken } ); | ||
|
||
// Select identityType and identityId entries that match the userId | ||
const identityProviders = await ctx.db.userIdentities.findMany({ | ||
where: { | ||
userId, | ||
}, | ||
select: { | ||
identityType: true, | ||
identityId: true, | ||
} | ||
}); | ||
|
||
return { identityProviders }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { ScriptContext, Empty, RuntimeError } from "../module.gen.ts"; | ||
import { IdentityDataInput, IdentityProviderInfo } from "../utils/types.ts"; | ||
|
||
export interface Request { | ||
userToken: string; | ||
info: IdentityProviderInfo; | ||
uniqueData?: IdentityDataInput; | ||
additionalData: IdentityDataInput; | ||
} | ||
|
||
export type Response = Empty; | ||
|
||
export async function run( | ||
ctx: ScriptContext, | ||
req: Request, | ||
): Promise<Response> { | ||
await ctx.modules.rateLimit.throttle({ | ||
key: req.userToken, | ||
period: 10, | ||
requests: 10, | ||
type: "user", | ||
}); | ||
|
||
// Ensure the user token is valid and get the user ID | ||
const { userId } = await ctx.modules.users.authenticateToken({ userToken: req.userToken } ); | ||
|
||
// Error if this identity provider is not associated with the user | ||
const { data: prevData } = await ctx.modules.identities.get({ userToken: req.userToken, info: req.info }); | ||
if (!prevData) throw new RuntimeError("identity_provider_not_found"); | ||
|
||
|
||
// Update the identity data where userId, identityType, and identityId match | ||
await ctx.db.userIdentities.update({ | ||
where: { | ||
userId_identityType_identityId: { | ||
userId, | ||
identityType: req.info.identityType, | ||
identityId: req.info.identityId, | ||
} | ||
}, | ||
data: { | ||
uniqueData: req.uniqueData, | ||
additionalData: req.additionalData, | ||
}, | ||
}); | ||
|
||
return {}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { ScriptContext } from "../module.gen.ts"; | ||
import { IdentityDataInput, IdentityProviderInfo } from "../utils/types.ts"; | ||
|
||
export interface Request { | ||
info: IdentityProviderInfo; | ||
uniqueData: IdentityDataInput; | ||
} | ||
|
||
export interface Response { | ||
userToken: string; | ||
} | ||
|
||
export async function run( | ||
ctx: ScriptContext, | ||
req: Request, | ||
): Promise<Response> { | ||
const key = req.info.identityType + ":" + req.info.identityId + ":" + JSON.stringify(req.uniqueData); | ||
await ctx.modules.rateLimit.throttle({ | ||
key, | ||
period: 10, | ||
requests: 10, | ||
type: "user", | ||
}); | ||
|
||
// Get users the provider is associated with | ||
const identity = await ctx.db.userIdentities.findFirst({ | ||
where: { | ||
identityType: req.info.identityType, | ||
identityId: req.info.identityId, | ||
uniqueData: { equals: req.uniqueData }, | ||
}, | ||
select: { | ||
userId: true, | ||
}, | ||
}); | ||
|
||
// If the provider info/uniqueData combo is not associated with a user, | ||
// throw provider_not_found error. | ||
if (!identity) { | ||
throw new Error("identity_not_found"); | ||
} | ||
|
||
// Generate a user token | ||
const { token: { token } } = await ctx.modules.users.createToken({ userId: identity.userId }); | ||
return { userToken: token }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { RuntimeError, ScriptContext } from "../module.gen.ts"; | ||
import { IdentityDataInput, IdentityProviderInfo } from "../utils/types.ts"; | ||
|
||
export interface Request { | ||
info: IdentityProviderInfo; | ||
uniqueData: IdentityDataInput; | ||
additionalData: IdentityDataInput; | ||
|
||
username?: string; | ||
} | ||
|
||
export interface Response { | ||
userToken: string; | ||
} | ||
|
||
export async function run( | ||
ctx: ScriptContext, | ||
req: Request, | ||
): Promise<Response> { | ||
try { | ||
return await ctx.modules.identities.signIn({ | ||
info: req.info, | ||
uniqueData: req.uniqueData, | ||
}); | ||
} catch (e) { | ||
if (e instanceof RuntimeError) { | ||
if (e.code === "identity_not_found") { | ||
return await ctx.modules.identities.signUp({ | ||
info: req.info, | ||
uniqueData: req.uniqueData, | ||
additionalData: req.additionalData, | ||
username: req.username, | ||
}); | ||
} | ||
} | ||
throw e; | ||
} | ||
} |
Oops, something went wrong.