Skip to content

Commit

Permalink
feat: add login form and oauth verification
Browse files Browse the repository at this point in the history
  • Loading branch information
limwa committed Jan 8, 2025
1 parent 4537c4e commit 0112aed
Show file tree
Hide file tree
Showing 14 changed files with 202 additions and 22 deletions.
12 changes: 6 additions & 6 deletions website/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ SMTP_PORT=1025
VITE_TZ=Europe/Lisbon
VITE_EVENT_COUNTDOWN_DATE=2025-04-11

GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
LINKEDIN_CLIENT_ID=
LINKEDIN_CLIENT_SECRET=
GITHUB_CLIENT_ID=********
GITHUB_CLIENT_SECRET=********
GOOGLE_CLIENT_ID=********
GOOGLE_CLIENT_SECRET=********
LINKEDIN_CLIENT_ID=********
LINKEDIN_CLIENT_SECRET=********
41 changes: 41 additions & 0 deletions website/app/controllers/authentication_controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { HttpContext } from '@adonisjs/core/http'

export default class AuthenticationController {
async login() {}

async initiateGithubLogin({ ally, inertia }: HttpContext) {
const url = await ally.use('github').redirectUrl()
return inertia.location(url)
}

async callbackForGithubLogin({ response, ally }: HttpContext) {
const github = ally.use('github')
const user = await github.user()

return response.json({ user })
}

async initiateGoogleLogin({ ally, inertia }: HttpContext) {
const url = await ally.use('google').redirectUrl()
return inertia.location(url)
}

async callbackForGoogleLogin({ response, ally }: HttpContext) {
const google = ally.use('google')
const user = await google.user()

return response.json({ user })
}

async initiateLinkedinLogin({ ally, inertia }: HttpContext) {
const url = await ally.use('linkedin').redirectUrl()
return inertia.location(url)
}

async callbackForLinkedinLogin({ response, ally }: HttpContext) {
const linkedin = ally.use('linkedin')
const user = await linkedin.user()

return response.json({ user })
}
}
8 changes: 8 additions & 0 deletions website/app/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const messages = {
auth: {
oauth: {
accessDenied: 'O pedido de início de sessão foi rejeitado.',
stateMismatch: 'Ocorreu um erro ao iniciar sessão. Por favor, tenta novamente.',
},
},
} as const
34 changes: 34 additions & 0 deletions website/app/middleware/verify_social_callback_middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { SocialProviders } from '@adonisjs/ally/types'
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
import { messages } from '../messages.js'

export default class VerifySocialCallbackMiddleware {
async handle(
{ response, session, ally }: HttpContext,
next: NextFn,
options: { provider: keyof SocialProviders }
) {
const oauth = ally.use(options.provider)

if (oauth.accessDenied()) {
session.flashErrors({ oauth: messages.auth.oauth.accessDenied })
return response.redirect('/login')
}

if (oauth.stateMisMatch()) {
session.flashErrors({ oauth: messages.auth.oauth.stateMismatch })
return response.redirect('/login')
}

const postRedirectError = oauth.getError()
if (postRedirectError) {
session.flashErrors({ oauth: postRedirectError })
return response.redirect('/login')
}

const output = await next()

return output
}
}
2 changes: 2 additions & 0 deletions website/config/ally.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const allyConfig = defineConfig({
clientId: env.get('GITHUB_CLIENT_ID'),
clientSecret: env.get('GITHUB_CLIENT_SECRET'),
callbackUrl: '',
scopes: ['user:email'],
allowSignup: false,
}),
google: services.google({
clientId: env.get('GOOGLE_CLIENT_ID'),
Expand Down
2 changes: 1 addition & 1 deletion website/config/inertia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const inertiaConfig = defineConfig({
* Data that should be shared with all rendered pages
*/
sharedData: {
errors: (ctx) => ctx.session?.flashMessages.get('errors'),
errors: (ctx) => ctx.session?.flashMessages.get('errorsBag'),
},

/**
Expand Down
3 changes: 2 additions & 1 deletion website/inertia/app/app.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/// <reference path="../../adonisrc.ts" />
/// <reference path="../../config/inertia.ts" />
/// <reference path="../../config/ally.ts" />
/// <reference path="../../config/auth.ts" />
/// <reference path="../../config/inertia.ts" />

import '../css/app.css'

Expand Down
3 changes: 2 additions & 1 deletion website/inertia/app/tuyau.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { createTuyau } from '@tuyau/client'
import { api } from '#.adonisjs/api'

console.log(import.meta.env.BASE_URL)
export const tuyau = createTuyau({
api,
baseUrl: import.meta.env.BASE_URL || 'http://localhost:3333',
baseUrl: 'http://localhost:3333',
})
29 changes: 29 additions & 0 deletions website/inertia/hooks/use_error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { usePage } from '@inertiajs/react'
import { useEffect, useMemo } from 'react'
import { Toast, toast } from './use_toast'

export function useError(key: string) {
const page = usePage()
const error = useMemo(() => {
const props = page.props
if (props.errors && key in props.errors) {
return props.errors[key]
}

return null
}, [page.props])

return error
}

export function useErrorToast(key: string, toastCreator: (msg: string) => Toast) {
const error = useError(key)
const toastContent = useMemo(() => error && toastCreator(error), [error])

useEffect(() => {
if (toastContent) {
const t = toast(toastContent)
return () => t.dismiss()
}
}, [toastContent])
}
2 changes: 1 addition & 1 deletion website/inertia/hooks/use_toast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ function dispatch(action: Action) {
})
}

type Toast = Omit<ToasterToast, 'id'>
export type Toast = Omit<ToasterToast, 'id'>

function toast({ ...props }: Toast) {
const id = genId()
Expand Down
13 changes: 6 additions & 7 deletions website/inertia/layouts/applayout.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { ReactElement } from 'react'
import React from 'react'
import { Head } from '@inertiajs/react'
import NavBar from '../components/navbar'
import { Toaster } from '~/components/ui/toaster'

type Props = {
title: string
children: ReactElement[]
className: string
children: React.ReactNode
className?: string
}

export default function AppLayout({ title, children, className }: Props) {
Expand All @@ -22,10 +23,8 @@ export default function AppLayout({ title, children, className }: Props) {
<div className="sticky left-0 right-0 top-0 z-30">
<NavBar />
</div>
<div className={'flex-1' + ' ' + className}>
{children}
<slot />
</div>
<div className={'flex-1' + ' ' + className}>{children}</div>
<Toaster />
</div>
</>
)
Expand Down
38 changes: 33 additions & 5 deletions website/inertia/pages/login.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { Button } from '~/components/ui/button'
import { Link } from '@tuyau/inertia/react'
import { Button, buttonVariants } from '~/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '~/components/ui/card'
import { Input } from '~/components/ui/input'
import { Label } from '~/components/ui/label'
import { Separator } from '~/components/ui/separator'
import { useError } from '~/hooks/use_error'
import { cn } from '~/lib/utils'

export default function Login() {
const oauthError = useError('oauth')

return (
// <AppLayout title="Iniciar Sessão">
<div className="flex items-center justify-center w-full h-dvh">
<div className="flex flex-col gap-6 max-w-sm">
<Card>
<Card className={cn(oauthError && 'border-red-600')}>
<CardHeader>
<CardTitle className="text-2xl">Iniciar Sessão</CardTitle>
<CardDescription>
Expand Down Expand Up @@ -43,9 +49,29 @@ export default function Login() {
<p className="text-sm text-muted-foreground min-w-max">Ou</p>
<Separator className="shrink" />
</div>
<Button variant="outline" className="w-full">
Iniciar Sessão com o Google
</Button>
<div className="grid grid-cols-3 gap-2">
<Link
route="auth.google.initiate"
className={cn(buttonVariants({ variant: 'outline' }), 'w-full')}
>
<span className="sr-only">Iniciar Sessão com o</span> Google
{/* <Google className="h-5 w-5" /> */}
</Link>
<Link
route="auth.github.initiate"
className={cn(buttonVariants({ variant: 'outline' }), 'w-full')}
>
<span className="sr-only">Iniciar Sessão com o</span> Github
{/* <Github className="h-5 w-5" /> */}
</Link>
<Link
route="auth.linkedin.initiate"
className={cn(buttonVariants({ variant: 'outline' }), 'w-full')}
>
<span className="sr-only">Iniciar Sessão com o</span> LinkedIn
{/* <LinkedIn className="h-5 w-5" /> */}
</Link>
</div>
</div>
</div>
<div className="mt-4 text-center text-sm">
Expand All @@ -57,7 +83,9 @@ export default function Login() {
</form>
</CardContent>
</Card>
{oauthError && <p className="text-sm text-red-600 text-center">{oauthError}</p>}
</div>
</div>
// </AppLayout>
)
}
1 change: 1 addition & 0 deletions website/start/kernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ router.use([
* the routes or the routes group.
*/
export const middleware = router.named({
verifySocialCallback: () => import('#middleware/verify_social_callback_middleware'),
guest: () => import('#middleware/guest_middleware'),
auth: () => import('#middleware/auth_middleware'),
})
36 changes: 36 additions & 0 deletions website/start/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,46 @@
|
*/
import router from '@adonisjs/core/services/router'
import { middleware } from '#start/kernel'

const AuthenticationController = () => import('#controllers/authentication_controller')
const TicketsController = () => import('#controllers/tickets_controller')

router.on('/').renderInertia('home')
router.on('/login').renderInertia('login')
router.get('/tickets', [TicketsController, 'index'])
router.on('/tickets/:id/checkout').renderInertia('payments').as('checkout')

router
.group(() => {
// Github
router
.get('/github/initiate', [AuthenticationController, 'initiateGithubLogin'])
.as('auth.github.initiate')

router
.get('/github/callback', [AuthenticationController, 'callbackForGithubLogin'])
.middleware(middleware.verifySocialCallback({ provider: 'github' }))
.as('auth.github.callback')

// Google
router
.get('/google/initiate', [AuthenticationController, 'initiateGoogleLogin'])
.as('auth.google.initiate')

router
.get('/google/callback', [AuthenticationController, 'callbackForGoogleLogin'])
.middleware(middleware.verifySocialCallback({ provider: 'google' }))
.as('auth.google.callback')

// LinkedIn
router
.get('/linkedin/initiate', [AuthenticationController, 'initiateLinkedinLogin'])
.as('auth.linkedin.initiate')

router
.get('/linkedin/callback', [AuthenticationController, 'callbackForLinkedinLogin'])
.middleware(middleware.verifySocialCallback({ provider: 'linkedin' }))
.as('auth.linkedin.callback')
})
.prefix('/auth')

0 comments on commit 0112aed

Please sign in to comment.