Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
DexterStorey committed Oct 3, 2024
0 parents commit 4e5f880
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 0 deletions.
16 changes: 16 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Release Package

on:
push:
branches:
- main

permissions:
contents: read
packages: write
id-token: write

jobs:
release:
uses: rubriclab/package/.github/workflows/release-package.yml@main
secrets: inherit
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- [2024-10-02] [init](https://github.com/RubricLab/auth/commit/40145b017976d9a7393063a4640e6965af45eac4)
# Changelog

4 changes: 4 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type {
AuthProvider,
AuthProviderConfig,
} from "./lib/types";
93 changes: 93 additions & 0 deletions lib/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { type NextRequest, NextResponse } from "next/server";
import type {
AuthProviders,
DB,
UserInfo,
AuthProvider,
AuthTokens,
} from "./types";

async function connectAuthProvider({
userId,
userInfo,
provider,
authTokens,
db,
}: {
userId: string;
userInfo: UserInfo;
provider: AuthProvider;
authTokens: AuthTokens;
db: DB;
}) {
await db.authProvider.upsert({
where: {
userId_provider_accountId: {
provider: provider.config.provider,
userId,
accountId: userInfo.accountId,
},
},
update: {
accessToken: authTokens.accessToken,
refreshToken: authTokens.refreshToken,
expiresAt: authTokens.expiresAt ? new Date(authTokens.expiresAt) : null,
},
create: {
provider: provider.config.provider,
accountId: userInfo.accountId,
label: userInfo.label,
scopes: provider.config.scopes,
accessToken: authTokens.accessToken,
refreshToken: authTokens.refreshToken,
expiresAt: authTokens.expiresAt ? new Date(authTokens.expiresAt) : null,
userId,
},
});
}

export function createAuthCallbackHandler({
authProviders,
db,
url,
}: {
authProviders: AuthProviders;
db: DB;
url: string;
}) {
return async (
request: NextRequest,
{ params }: { params: { provider: keyof AuthProviders } },
): Promise<NextResponse> => {
const provider = authProviders[params.provider];
if (!provider) {
return NextResponse.redirect(`${url}?error=InvalidProvider`);
}
const code = request.nextUrl.searchParams.get("code");
if (!code) {
return NextResponse.redirect(`${url}?error=NoCodeProvided`);
}

const userId = request.nextUrl.searchParams.get("state");
if (!userId) {
return NextResponse.redirect(`${url}?error=NoUserIdProvided`);
}

try {
const authTokens = await provider.getTokensFromCode({ code });
const userInfo = await provider.getUserInfo({
token: authTokens.accessToken,
});

await connectAuthProvider({ userId, provider, authTokens, userInfo, db });

return NextResponse.redirect(`${url}?success=true`);
} catch (error) {
console.error(
`Error during ${provider.config.provider} auth callback:`,
error,
);
return NextResponse.redirect(`${url}?error=AuthFailed`);
}
};
}
49 changes: 49 additions & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { PrismaClient } from "@prisma/client";

export interface UserInfo {
accountId: string;
label: string;
}

export interface AuthProviderConfig {
provider: string;
scopes: string[];
clientId: string;
clientSecret: string;
}

export interface AuthTokens {
accessToken: string;
refreshToken: string | null;
expiresAt: number | null;
}

export interface AuthProvider {
config: AuthProviderConfig;
getAuthUrl: ({ userId }: { userId: string }) => string;
getTokensFromCode({ code }: { code: string }): Promise<AuthTokens>;
getAccessToken({
userId,
accountId,
}: {
userId: string;
accountId: string;
}): Promise<{ accessToken: string }>;
getUserInfo({ token }: { token: string }): Promise<UserInfo>;
}

export type AuthProviders = {
[provider in string]?: AuthProvider;
};

export interface User {
id: string;
}

export interface WebhookInfo {
id: string;
type: string;
enabled: boolean;
}

export type DB = PrismaClient;
59 changes: 59 additions & 0 deletions lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { Prisma } from "@prisma/client";

import type { AuthProviders } from "./types";

export function createAuthActions({
authProviders,
db,
}: {
authProviders: AuthProviders;
db: {
authProvider: Prisma.AuthProviderDelegate;
};
}) {
return {
async getAuthUrl({
userId,
provider,
}: {
userId: string;
provider: string;
}) {
const authProvider = authProviders[provider];

if (!authProvider) {
throw new Error(`Auth provider not found for ${provider}`);
}

return authProvider.getAuthUrl({ userId });
},

async getConnectedAccounts({ userId }: { userId: string }) {
const connectedAccounts = await db.authProvider.findMany({
where: { userId },
});

return connectedAccounts;
},

async disconnectAuthProvider({
userId,
provider,
accountId,
}: {
userId: string;
provider: string;
accountId: string;
}) {
await db.authProvider.delete({
where: {
userId_provider_accountId: {
userId,
provider,
accountId,
},
},
});
},
};
}
24 changes: 24 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"scripts": {
"generate": "bun scripts/generate.ts",
"watch": "bun scripts/watch.ts",
"prepare": "bun x simple-git-hooks",
"bleed": "bun x npm-check-updates -u && bun i",
"clean": "rm -rf .next && rm -rf node_modules",
"format": "bun x biome format --write .",
"lint": "bun x biome check .",
"lint:fix": "bun x biome lint . --write --unsafe"
},
"name": "@rubriclab/auth",
"version": "0.0.1",
"main": "index.ts",
"dependencies": {
"@rubriclab/package": "*"
},
"simple-git-hooks": {
"post-commit": "bun run rubriclab-postcommit"
},
"publishConfig": {
"access": "public"
}
}

0 comments on commit 4e5f880

Please sign in to comment.