Skip to content

Commit c104723

Browse files
「Googleでログイン」を実装する #19
1 parent 11c6ebc commit c104723

File tree

4 files changed

+158
-29
lines changed

4 files changed

+158
-29
lines changed

.vscode/settings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
{
22
"cSpell.words": [
3+
"builtins",
34
"instanceof",
45
"minlength",
56
"onfocus",
7+
"signin",
68
"sveltejs",
79
"sveltekit"
810
]

src/routes/+layout.svelte

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
<!--
21
<script lang="ts">
32
import { page } from '$app/stores'
43
</script>
5-
-->
64

75
<svelte:head>
8-
<title> SvelteKit Authentication</title>
6+
<title>SvelteKit Authentication</title>
97
</svelte:head>
108

119
<!-- <nav>
@@ -21,6 +19,19 @@
2119
{/if}
2220
</nav> -->
2321

22+
23+
24+
{#if $page.url.pathname != '/login' && !$page.data.user}
25+
<script src="https://accounts.google.com/gsi/client" async defer></script>
26+
<div
27+
id="g_id_onload"
28+
data-client_id="417364210012-9h5sc3ccdt8gsi2e4o2n01j55bcg0grn.apps.googleusercontent.com"
29+
data-login_uri="/pin_code?/google"
30+
data-auto_select="false"
31+
data-redirect_url={$page.url.pathname}
32+
/>
33+
{/if}
34+
2435
<main>
2536
<slot />
26-
</main>
37+
</main>

src/routes/login/+page.svelte

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,87 @@
11
<script lang="ts">
2+
import { browser } from '$app/environment'
23
import { page } from '$app/stores'
34
import { onMount } from 'svelte'
4-
5+
56
let first_element: HTMLInputElement
67
78
const redirect_url = $page.url.searchParams.get('redirect_url') ?? ''
89
const encoded_redirect_url = encodeURIComponent(redirect_url)
910
11+
// // NOTE: https://oc-technote.com/javascript/javascript-post-params-move-to-page/
12+
// // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-function-return-type
13+
// function post(params: any) {
14+
// const form = document.createElement('form')
15+
// form.method = 'post'
16+
// form.action = '/pin_code?/google'
17+
18+
// for (const key in params) {
19+
// // eslint-disable-next-line no-prototype-builtins
20+
// if (params.hasOwnProperty(key)) {
21+
// const hiddenField = document.createElement('input')
22+
// hiddenField.type = 'hidden'
23+
// hiddenField.name = key
24+
// hiddenField.value = params[key]
25+
26+
// form.appendChild(hiddenField)
27+
// }
28+
// }
29+
30+
// document.body.appendChild(form)
31+
// form.submit()
32+
// }
33+
34+
// function handleCredentialResponse(response: any): void {
35+
// post({ credential: response.credential })
36+
// }
37+
1038
onMount(() => {
11-
document.onfocus = (event) => {
39+
document.onfocus = (event): void => {
1240
if (event.target instanceof HTMLInputElement) event.target.select()
1341
}
1442
1543
first_element.select()
44+
45+
// google.accounts.id.initialize({
46+
// client_id: '417364210012-9h5sc3ccdt8gsi2e4o2n01j55bcg0grn.apps.googleusercontent.com',
47+
// callback: handleCredentialResponse,
48+
// // auto_select: true
49+
// })
50+
51+
// google.accounts.id.renderButton(
52+
// document.getElementById('buttonDiv'),
53+
// { theme: 'outline', size: 'large', width: '240' } // customization attributes
54+
// )
55+
56+
// google.accounts.id.prompt() // also display the One Tap dialog
1657
})
1758
</script>
1859

1960
<h1>Log in / Register</h1>
61+
{#if browser}
62+
<script src="https://accounts.google.com/gsi/client" async defer></script>
63+
{/if}
64+
65+
<div
66+
id="g_id_onload"
67+
data-client_id="417364210012-9h5sc3ccdt8gsi2e4o2n01j55bcg0grn.apps.googleusercontent.com"
68+
data-login_uri="/pin_code?/google"
69+
data-auto_prompt="true"
70+
data-auto_select="true"
71+
/>
72+
<div
73+
class="g_id_signin"
74+
data-width="240"
75+
data-type="standard"
76+
data-size="large"
77+
data-theme="outline"
78+
data-text="sign_in_with"
79+
data-shape="rectangular"
80+
data-logo_alignment="left"
81+
/>
2082

83+
<!-- <div id="buttonDiv" style="max-width:400" /> -->
84+
<br />
2185
<form method="POST" action="/pin_code?/login&redirect_url={encoded_redirect_url}">
2286
<input type="email" name="email" placeholder="Email" required bind:this={first_element} />
2387

src/routes/pin_code/+page.server.ts

Lines changed: 75 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { db } from '$lib/database'
33
import { NodemailerManager as NodeMailerManager } from '$lib/nodemailer_manager'
44
import type { PageServerLoad } from '.svelte-kit/types/src/routes/$types'
55
import type { User } from '@prisma/client'
6-
import { invalid, redirect, type Action, type Actions } from '@sveltejs/kit'
6+
import { invalid, redirect, type Actions } from '@sveltejs/kit'
77

88
enum Roles {
99
admin = 'admin',
@@ -48,8 +48,8 @@ async function sendMail(user: User, pin_code: string): Promise<void> {
4848
async function findUser(email: string, can_register = true): Promise<User | undefined> {
4949
const user = await db.user.findUnique({ where: { email } })
5050

51-
if (user) return user;
52-
if (!can_register) return undefined;
51+
if (user) return user
52+
if (!can_register) return undefined
5353

5454
try {
5555
return await db.user.create({
@@ -64,34 +64,52 @@ async function findUser(email: string, can_register = true): Promise<User | unde
6464
}
6565
}
6666

67-
async function login(request: Request, can_register = true): Promise<Action> {
68-
const data = await request.formData()
69-
const email = data.get('email')?.toString() ?? ''
67+
type GoogleCredential = {
68+
sub: string
69+
name: string
70+
given_name: string
71+
family_name: string
72+
picture: string
73+
email: string
74+
}
7075

71-
if (!email) throw redirect(302, '/')
76+
function decodeJwtResponse(credential: string): GoogleCredential {
77+
const base64Url = credential.split('.')[1]
78+
const base64 = base64Url?.replace(/-/g, '+').replace(/_/g, '/') ?? ''
79+
const jsonPayload = decodeURIComponent(
80+
atob(base64)
81+
.split('')
82+
.map(function (c) {
83+
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
84+
})
85+
.join('')
86+
)
87+
return JSON.parse(jsonPayload) as GoogleCredential
88+
}
7289

73-
const user = await findUser(email, can_register)
90+
export const actions: Actions = {
91+
login: async ({ request }) => {
92+
const data = await request.formData()
93+
const email = data.get('email')?.toString() ?? ''
7494

75-
if (!user) return { success: true, email, missing: false, credentials: false }
95+
if (!email) throw redirect(302, '/')
7696

77-
const pin_code = createPinCode()
78-
console.log('sendmail')
79-
sendMail(user, pin_code)
97+
const user = await findUser(email, true)
8098

81-
const user_id = user.id
99+
if (!user) return { credentials: true, email, missing: false, success: false }
82100

83-
const a = await db.authPin.upsert({
84-
where: { user_id },
85-
update: { pin_code },
86-
create: { user_id, pin_code },
87-
})
101+
const pin_code = createPinCode()
102+
sendMail(user, pin_code)
88103

89-
return { success: true, email, missing: false, credentials: false }
90-
}
104+
const user_id = user.id
91105

92-
export const actions: Actions = {
93-
login: async ({ request }) => {
94-
return await login(request)
106+
await db.authPin.upsert({
107+
where: { user_id },
108+
update: { pin_code },
109+
create: { user_id, pin_code },
110+
})
111+
112+
return { success: true, email, missing: false, credentials: false }
95113
},
96114
submit: async ({ cookies, request }) => {
97115
const data = await request.formData()
@@ -135,4 +153,38 @@ export const actions: Actions = {
135153

136154
return { success: true, email }
137155
},
156+
google: async ({ cookies, request }) => {
157+
const data = await request.formData()
158+
const credential = data.get('credential')?.toString() ?? ''
159+
160+
console.log('Encoded JWT ID token: ' + credential)
161+
162+
if (!credential) return invalid(400, { message: 'Invalid credential' })
163+
164+
const payload = decodeJwtResponse(credential)
165+
166+
console.log('ID ' + payload.sub)
167+
console.log('Full Name: ' + payload.name)
168+
console.log('Given Name: ' + payload.given_name)
169+
console.log('Family Name: ' + payload.family_name)
170+
console.log('Image URL: ' + payload.picture)
171+
console.log('Email: ' + payload.email)
172+
173+
const email = payload.email as string
174+
const user = await findUser(email, true)
175+
176+
if (!user) return { credentials: true, email, missing: false }
177+
178+
const user_id = user.id
179+
180+
const auth_token = await db.authToken.upsert({
181+
where: { user_id },
182+
update: { token: crypto.randomUUID() },
183+
create: { user_id, token: crypto.randomUUID() },
184+
})
185+
186+
new CookiesManager(cookies).setSessionId(auth_token.token)
187+
188+
redirect(302, '/login')
189+
},
138190
}

0 commit comments

Comments
 (0)