Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: sso login github
Browse files Browse the repository at this point in the history
zekhoi committed Aug 25, 2024
1 parent 83cba9f commit c39f185
Showing 9 changed files with 55 additions and 17 deletions.
49 changes: 39 additions & 10 deletions src/app/api/auth/github/callback/route.ts
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ export interface GitHubUser {
login: string;
avatar_url: string;
email: string;
name: string;
}

interface Email {
@@ -28,26 +29,51 @@ export async function GET(request: Request): Promise<Response> {
const url = new URL(request.url);
const code = url.searchParams.get('code');
const state = url.searchParams.get('state');
const storedState = cookies().get('github_oauth_state')?.value ?? null;
if (!code || !state || !storedState || state !== storedState) {
return new Response(null, {
status: 400,
});

if (!code || !state) {
return Response.json(
{ error: 'Invalid request' },
{
status: 400,
},
);
}

const savedState = cookies().get('github_oauth_state')?.value;

if (!savedState) {
return Response.json(
{ error: 'saved state is not exists' },
{
status: 400,
},
);
}

if (savedState !== state) {
return Response.json(
{
error: 'State does not match',
},
{
status: 400,
},
);
}

try {
const tokens = await github.validateAuthorizationCode(code);
const { accessToken } = await github.validateAuthorizationCode(code);
const githubUserResponse = await fetch('https://api.github.com/user', {
headers: {
Authorization: `Bearer ${tokens.accessToken}`,
Authorization: `Bearer ${accessToken}`,
},
method: 'GET',
});
const githubUser: GitHubUser = await githubUserResponse.json();

const existingAccount = await getUserByGithubId(githubUser.id);

if (existingAccount) {
await setSession(existingAccount.accountId);
await setSession(existingAccount.id);
return new Response(null, {
status: 302,
headers: {
@@ -61,7 +87,7 @@ export async function GET(request: Request): Promise<Response> {
'https://api.github.com/user/emails',
{
headers: {
Authorization: `Bearer ${tokens.accessToken}`,
Authorization: `Bearer ${accessToken}`,
},
},
);
@@ -73,7 +99,10 @@ export async function GET(request: Request): Promise<Response> {
const userId = await createUserViaGithub({
accountId: githubUser.id,
email: githubUser.email,
name: githubUser.name,
avatar: githubUser.avatar_url,
});

await setSession(userId);
return new Response(null, {
status: 302,
1 change: 1 addition & 0 deletions src/app/api/auth/google/callback/route.ts
Original file line number Diff line number Diff line change
@@ -56,6 +56,7 @@ export async function GET(request: Request): Promise<Response> {
const userId = await createUserViaGoogle({
accountId: googleUser.sub,
email: googleUser.email,
name: googleUser.name,
});
await setSession(userId);
return new Response(null, {
4 changes: 2 additions & 2 deletions src/database/schema/session.schema.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { pgTable, timestamp, uuid } from 'drizzle-orm/pg-core';
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';

import { user } from './user.schema';

export const session = pgTable('session', {
id: uuid('id').primaryKey(),
id: text('id').primaryKey(),
userId: uuid('user_id')
.notNull()
.references(() => user.id),
3 changes: 2 additions & 1 deletion src/database/schema/user.schema.ts
Original file line number Diff line number Diff line change
@@ -7,14 +7,15 @@ export const accountTypeEnum = pgEnum('type', ['google', 'github']);

export const user = pgTable('users', {
id: uuid('id').primaryKey().$defaultFn(uuidv7),
name: text('name').notNull(),
avatar: text('avatar'),
accountType: accountTypeEnum('account_type').notNull(),
accountId: text('account_id').unique().notNull(),
email: text('email').unique().notNull(),
role: uuid('role')
.notNull()
.references(() => role.id),
createdAt: timestamp('created_at').defaultNow().notNull(),
lastLogin: timestamp('last_login').defaultNow().notNull(),
});

export type User = typeof user.$inferSelect;
2 changes: 2 additions & 0 deletions src/database/seed.ts
Original file line number Diff line number Diff line change
@@ -29,12 +29,14 @@ async function main() {
{
accountId: 'google:1234567890',
email: '[email protected]',
name: 'Joko Widodo',
accountType: 'google',
role: adminRole.id,
},
{
accountId: 'github:1234567890',
email: '[email protected]',
name: 'Prabowo Subianto',
accountType: 'github',
role: userRole.id,
},
2 changes: 1 addition & 1 deletion src/env.ts
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ const envSchema = zod.object({
NEXT_PUBLIC_URL: zod.string().default('http://localhost:3000'),
PORT: zod.string().default('3000'),
// Database
DATABASE_URL: zod.string(),
DATABASE_URL: zod.string().default(''),
// SSO settings
GITHUB_CLIENT_ID: zod.string().default(''),
GITHUB_CLIENT_SECRET: zod.string().default(''),
4 changes: 4 additions & 0 deletions src/lib/auth/account.ts
Original file line number Diff line number Diff line change
@@ -5,6 +5,8 @@ import { database, role, user } from '@/database';
export type CreateUserProps = {
accountId: string;
email: string;
name: string;
avatar?: string;
};
export async function createUser(
data: CreateUserProps,
@@ -25,6 +27,8 @@ export async function createUser(
accountId: data.accountId,
email: data.email,
role: userRole.id,
name: data.name,
avatar: data.avatar,
})
.onConflictDoNothing()
.returning();
3 changes: 3 additions & 0 deletions src/lib/auth/index.ts
Original file line number Diff line number Diff line change
@@ -74,6 +74,9 @@ export const validateRequest = cache(
export const github = new GitHub(
env.GITHUB_CLIENT_ID,
env.GITHUB_CLIENT_SECRET,
{
redirectURI: `${env.NEXT_PUBLIC_URL}/api/auth/github/callback`,
},
);

export const google = new Google(
4 changes: 1 addition & 3 deletions src/lib/auth/session.ts
Original file line number Diff line number Diff line change
@@ -23,9 +23,7 @@ export const assertAuthenticated = async () => {
};

export async function setSession(userId: UserId) {
const session = await lucia.createSession(userId, {
role: 'user',
});
const session = await lucia.createSession(userId, {});
const sessionCookie = lucia.createSessionCookie(session.id);
cookies().set(
sessionCookie.name,

0 comments on commit c39f185

Please sign in to comment.