-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: finish email confirmation flow, login and register
- Loading branch information
Showing
44 changed files
with
551 additions
and
191 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,9 +15,13 @@ SESSION_DRIVER=cookie | |
|
||
FROM_EMAIL=[email protected] | ||
REPLY_TO_EMAIL=[email protected] | ||
SMTP_HOST=localhost | ||
SMTP_PORT=1025 | ||
|
||
# Rate limiting | ||
LIMITER_STORE=memory | ||
|
||
# Ally | ||
GITHUB_CLIENT_ID=******** | ||
GITHUB_CLIENT_SECRET=******** | ||
|
@@ -30,4 +34,6 @@ LINKEDIN_CLIENT_SECRET=******** | |
INERTIA_PUBLIC_TZ=Europe/Lisbon | ||
INERTIA_PUBLIC_EVENT_COUNTDOWN_DATE=2025-04-11 | ||
INERTIA_PUBLIC_APP_URL=http://127.0.0.1:3333 | ||
LIMITER_STORE=redis | ||
REDIS_HOST=127.0.0.1 | ||
REDIS_PORT=6379 | ||
REDIS_PASSWORD= |
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 |
---|---|---|
@@ -1,89 +1,114 @@ | ||
import Account from '#models/account' | ||
import { socialAccountLoginValidator } from '#validators/account' | ||
import User from '#models/user' | ||
import type { HttpContext } from '@adonisjs/core/http' | ||
import { registerWithCredentialsValidator } from '#validators/authentication' | ||
import { | ||
registerWithCredentialsValidator, | ||
emailVerificationCallbackValidator, | ||
loginWithCredentialsValidator, | ||
} from '#validators/authentication' | ||
import { UserService } from '#services/user_service' | ||
import { inject } from '@adonisjs/core' | ||
import UserCreated from '#events/user_created' | ||
import SendVerificationEmail from '#listeners/send_verification_email' | ||
import { errors } from '@adonisjs/auth' | ||
|
||
@inject() | ||
export default class AuthenticationController { | ||
async login({ request, auth, response, session }: HttpContext) { | ||
const { email, password } = request.only(['email', 'password']) | ||
constructor(private userService: UserService) {} | ||
|
||
async login({ request, auth, session, response }: HttpContext) { | ||
const { email, password } = await request.validateUsing(loginWithCredentialsValidator) | ||
|
||
try { | ||
const account = await Account.verifyCredentials(email, password) | ||
const account = await Account.verifyCredentials(`credentials:${email}`, password) | ||
|
||
const user = await User.query().where('id', account.userId).first() | ||
if (user) await auth.use('web').login(user) | ||
await account.load('user') | ||
await auth.use('web').login(account.user) | ||
|
||
response.redirect('/') | ||
return response.redirect().toRoute('pages:home') | ||
|
||
} catch (error) { | ||
session.flash('errors', { oauth: 'Email ou palavra-passe incorretos' }) | ||
return response.redirect().back() | ||
if (error instanceof errors.E_INVALID_CREDENTIALS) { | ||
session.flashErrors({ password: 'As credenciais que introduziste não são válidas' }) | ||
return response.redirect().back() | ||
} | ||
} | ||
} | ||
|
||
@inject() | ||
async register({ request, auth, response }: HttpContext, userService: UserService) { | ||
async logout({ auth, response }: HttpContext) { | ||
await auth.use('web').logout() | ||
return response.redirect().toRoute('pages:home') | ||
} | ||
|
||
async register({ request, auth, response }: HttpContext) { | ||
const { email, password } = await request.validateUsing(registerWithCredentialsValidator) | ||
|
||
const user = await userService.createUserWithCredentials(email, password) | ||
const user = await this.userService.createUserWithCredentials(email, password) | ||
await auth.use('web').login(user) | ||
|
||
return response.redirect().toRoute('auth.email-confirmation.show') | ||
return response.redirect().toRoute('pages:auth.verify') | ||
} | ||
|
||
async showEmailConfirmation({ inertia }: HttpContext) { | ||
return inertia.render('email_confirmation') | ||
} | ||
async retryEmailVerification({ auth, response }: HttpContext) { | ||
const user = auth.getUserOrFail() | ||
|
||
async verify({ request, view }: HttpContext) { | ||
if (request.method() === 'POST') return request.toJSON() | ||
return view.render('automatic_submit') | ||
const listener = new SendVerificationEmail() | ||
listener.handle(new UserCreated(user)) | ||
|
||
return response.redirect().toRoute('pages:auth.verify') | ||
} | ||
|
||
async initiateGithubLogin({ ally, inertia }: HttpContext) { | ||
const url = await ally.use('github').redirectUrl() | ||
console.log(url) | ||
return inertia.location(url) | ||
async callbackForEmailVerification({ request, view, response }: HttpContext) { | ||
if (request.method() !== 'POST') return view.render('automatic_submit') | ||
|
||
const { email } = await request.validateUsing(emailVerificationCallbackValidator) | ||
await this.userService.verifyEmail(email) | ||
|
||
return response.redirect().toRoute('actions:auth.verify.success') | ||
} | ||
|
||
async callbackForGithubLogin({ ally }: HttpContext) { | ||
const github = ally.use('github') | ||
const user = await github.user() | ||
// SOCIAL AUTHENTICATION | ||
|
||
const data = await socialAccountLoginValidator.validate(user) | ||
console.log(data) | ||
// async initiateGithubLogin({ ally, inertia }: HttpContext) { | ||
// const url = await ally.use('github').redirectUrl() | ||
// return inertia.location(url) | ||
// } | ||
|
||
// const account = await getOrCreate({ | ||
// provider: 'github', | ||
// providerId: data.id, | ||
// }) | ||
// async callbackForGithubLogin({ ally }: HttpContext) { | ||
// const github = ally.use('github') | ||
// const user = await github.user() | ||
|
||
// return response.json({ user, account: account.serialize() }) | ||
} | ||
// const data = await socialAccountLoginValidator.validate(user) | ||
// console.log(data) | ||
|
||
async initiateGoogleLogin({ ally, inertia }: HttpContext) { | ||
const url = await ally.use('google').redirectUrl() | ||
return inertia.location(url) | ||
} | ||
// const account = await getOrCreate({ | ||
// provider: 'github', | ||
// providerId: data.id, | ||
// }) | ||
|
||
async callbackForGoogleLogin({ response, ally }: HttpContext) { | ||
const google = ally.use('google') | ||
const user = await google.user() | ||
// return response.json({ user, account: account.serialize() }) | ||
// } | ||
|
||
return response.json({ user }) | ||
} | ||
// async initiateGoogleLogin({ ally, inertia }: HttpContext) { | ||
// const url = await ally.use('google').redirectUrl() | ||
// return inertia.location(url) | ||
// } | ||
|
||
async initiateLinkedinLogin({ ally, inertia }: HttpContext) { | ||
const url = await ally.use('linkedin').redirectUrl() | ||
return inertia.location(url) | ||
} | ||
// async callbackForGoogleLogin({ response, ally }: HttpContext) { | ||
// const google = ally.use('google') | ||
// const user = await google.user() | ||
|
||
async callbackForLinkedinLogin({ response, ally }: HttpContext) { | ||
const linkedin = ally.use('linkedin') | ||
const user = await linkedin.user() | ||
// return response.json({ 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 }) | ||
// } | ||
} |
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,8 @@ | ||
import type User from '#models/user' | ||
import { BaseEvent } from '@adonisjs/core/events' | ||
|
||
export default class UserEmailVerified extends BaseEvent { | ||
constructor(public readonly user: User) { | ||
super() | ||
} | ||
} |
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 |
---|---|---|
@@ -1,29 +1,23 @@ | ||
import UserCreated from '#events/user_created' | ||
import EmailVerificationNotification from '#mails/email_verification_notification' | ||
import env from '#start/env' | ||
import app from '@adonisjs/core/services/app' | ||
import UserCreated from "#events/user_created"; | ||
import EmailVerificationNotification from "#mails/email_verification_notification"; | ||
import mail from "@adonisjs/mail/services/main"; | ||
import { buildUrl, staticUrl } from "../url.js"; | ||
|
||
export default class SendVerificationEmail { | ||
async handle(event: UserCreated) { | ||
// Don't send the verification e-mail if the user has already verified it | ||
if (event.user.emailVerifiedAt) return | ||
|
||
const mailer = await app.container.make('mail.manager') | ||
const router = await app.container.make('router') | ||
|
||
const email = event.user.email | ||
if (event.user.emailVerifiedAt) return; | ||
|
||
const email = event.user.email; | ||
const notification = new EmailVerificationNotification({ | ||
email, | ||
logoUrl: '/images/logo-white.svg', | ||
logoUrl: staticUrl("/images/logo-white.png"), | ||
|
||
verificationLink: router | ||
.builder() | ||
verificationLink: buildUrl() | ||
.qs({ email }) | ||
.prefixUrl(env.get("INERTIA_PUBLIC_APP_URL")) | ||
.makeSigned('auth.verify', { expiresIn: '1h' }), | ||
}) | ||
.makeSigned("actions:auth.verify.callback", { expiresIn: "1h" }), | ||
}); | ||
|
||
await mailer.send(notification) | ||
await mail.send(notification); | ||
} | ||
} |
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 |
---|---|---|
@@ -1,15 +1,14 @@ | ||
import { ReactNotification } from './base/react_notification.js' | ||
import type { EmailVerificationProps } from "#resources/emails/authentication/email_verification" | ||
import type { EmailVerificationProps } from '#resources/emails/authentication/email_verification' | ||
|
||
export default class EmailVerificationNotification extends ReactNotification { | ||
constructor( | ||
private props: EmailVerificationProps | ||
) { | ||
constructor(private props: EmailVerificationProps) { | ||
super() | ||
} | ||
|
||
async prepare() { | ||
this.message.to(this.props.email) | ||
this.message.to(this.props.email).subject('Confirma o teu e-mail!') | ||
|
||
await this.jsx(() => import('#resources/emails/authentication/email_verification'), this.props) | ||
} | ||
} |
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,13 @@ | ||
import type { HttpContext } from '@adonisjs/core/http' | ||
import type { NextFn } from '@adonisjs/core/types/http' | ||
|
||
export default class VerifyUrlSignatureMiddleware { | ||
async handle({request, response}: HttpContext, next: NextFn) { | ||
if (!request.hasValidSignature()) { | ||
return response.badRequest("Invalid or expired URL") | ||
} | ||
|
||
const output = await next() | ||
return output | ||
} | ||
} |
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
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,12 @@ | ||
import env from "#start/env"; | ||
import router from "@adonisjs/core/services/router"; | ||
|
||
const base = env.get("INERTIA_PUBLIC_APP_URL"); | ||
|
||
export function staticUrl(path: string) { | ||
return new URL(path, base).toString(); | ||
} | ||
|
||
export function buildUrl() { | ||
return router.builder().prefixUrl(base); | ||
} |
Oops, something went wrong.