-
Notifications
You must be signed in to change notification settings - Fork 84
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat[auth]: add login with google (#35)
* update package-lock.json * add GOOGLE_CLIENT_ID & GOOGLE_CLIENT_SECRET env * add continue to with google button * add google provider * add BASE_URL * add auth controller * clean up * add login with google * use link as button child
- Loading branch information
1 parent
d8b3679
commit 44bd032
Showing
7 changed files
with
215 additions
and
66 deletions.
There are no files selected for viewing
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
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,80 @@ | ||
import { PROVIDER, google } from "@/lib/auth"; | ||
import { cookies } from "next/headers"; | ||
import { OAuth2RequestError } from "arctic"; | ||
import * as AuthController from "@/controllers/auth"; | ||
|
||
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("google_oauth_state")?.value ?? null; | ||
const storedCodeVerifier = | ||
cookies().get("google_oauth_code_verifier")?.value ?? null; | ||
if ( | ||
!code || | ||
!state || | ||
!storedState || | ||
!storedCodeVerifier || | ||
state !== storedState | ||
) { | ||
return new Response(null, { | ||
status: 400, | ||
}); | ||
} | ||
|
||
try { | ||
const token = await google.validateAuthorizationCode( | ||
code, | ||
storedCodeVerifier | ||
); | ||
|
||
const resp = await fetch( | ||
"https://openidconnect.googleapis.com/v1/userinfo", | ||
{ | ||
headers: { | ||
Authorization: `Bearer ${token.accessToken}`, | ||
}, | ||
} | ||
); | ||
const googleUser: GoogleUser = await resp.json(); | ||
|
||
await AuthController.save( | ||
{ | ||
id: googleUser.sub, | ||
name: googleUser.name, | ||
email: googleUser.email, | ||
}, | ||
PROVIDER.GOOGLE | ||
); | ||
|
||
return new Response(null, { | ||
status: 302, | ||
headers: { | ||
Location: "/", | ||
}, | ||
}); | ||
} catch (e) { | ||
console.error(e); | ||
// the specific error message depends on the provider | ||
if (e instanceof OAuth2RequestError) { | ||
// invalid code | ||
return new Response(null, { | ||
status: 400, | ||
}); | ||
} | ||
return new Response(null, { | ||
status: 500, | ||
}); | ||
} | ||
} | ||
|
||
interface GoogleUser { | ||
sub: string; | ||
name: string; | ||
given_name: string; | ||
picture: string; | ||
email: string; | ||
email_verified: boolean; | ||
locale: string; | ||
} |
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,29 @@ | ||
import { google } from "@/lib/auth"; | ||
import { generateCodeVerifier, generateState } from "arctic"; | ||
import { cookies } from "next/headers"; | ||
|
||
export async function GET(): Promise<Response> { | ||
const state = generateState(); | ||
const codeVerifier = generateCodeVerifier(); | ||
const url = await google.createAuthorizationURL(state, codeVerifier, { | ||
scopes: ["profile", "email"], | ||
}); | ||
|
||
cookies().set("google_oauth_state", state, { | ||
path: "/", | ||
secure: process.env.NODE_ENV === "production", | ||
httpOnly: true, | ||
maxAge: 60 * 10, | ||
sameSite: "lax", | ||
}); | ||
|
||
cookies().set("google_oauth_code_verifier", codeVerifier, { | ||
path: "/", | ||
secure: process.env.NODE_ENV === "production", | ||
httpOnly: true, | ||
maxAge: 60 * 10, | ||
sameSite: "lax", | ||
}); | ||
|
||
return Response.redirect(url); | ||
} |
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
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,67 @@ | ||
import { db } from "@/db"; | ||
import { user, user_oauth } from "@/db/schema"; | ||
import { Provider, lucia } from "@/lib/auth"; | ||
import { generateId } from "lucia"; | ||
import { cookies, headers } from "next/headers"; | ||
|
||
interface UserAuth { | ||
id: string; | ||
name: string; | ||
email: string; | ||
} | ||
|
||
export const save = async (data: UserAuth, provider: Provider) => { | ||
const { id, name, email } = data; | ||
const headerStore = headers(); | ||
|
||
const existingUser = await db.query.user_oauth.findFirst({ | ||
where: (field, op) => | ||
op.and(op.eq(field.provider, provider), op.eq(field.providerId, id)), | ||
}); | ||
|
||
if (existingUser?.userId) { | ||
const session = await lucia.createSession(existingUser.userId, { | ||
auth_id: existingUser.id, | ||
user_agent: headerStore.get("user-agent"), | ||
}); | ||
|
||
const sessionCookie = lucia.createSessionCookie(session.id); | ||
|
||
cookies().set( | ||
sessionCookie.name, | ||
sessionCookie.value, | ||
sessionCookie.attributes | ||
); | ||
|
||
return new Response(null, { | ||
status: 302, | ||
headers: { | ||
Location: "/connect", | ||
}, | ||
}); | ||
} | ||
|
||
const userId = generateId(15); | ||
const authId = generateId(15); | ||
|
||
await db.insert(user).values({ id: userId, name, email }); | ||
await db.insert(user_oauth).values({ | ||
id: authId, | ||
provider: provider, | ||
providerId: id, | ||
userId: userId, | ||
}); | ||
|
||
const session = await lucia.createSession(userId, { | ||
auth_id: userId, | ||
user_agent: headerStore.get("user-agent"), | ||
}); | ||
|
||
const sessionCookie = lucia.createSessionCookie(session.id); | ||
|
||
cookies().set( | ||
sessionCookie.name, | ||
sessionCookie.value, | ||
sessionCookie.attributes | ||
); | ||
}; |
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
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