This commit is contained in:
Kiss Dávid
2025-07-18 10:27:33 +02:00
commit 590adb271d
40 changed files with 8792 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
import type { HttpContext } from '@adonisjs/core/http'
import { registerValidator, loginValidator } from '#validators/auth'
import User from '#models/user'
import logger from '@adonisjs/core/services/logger'
export default class AuthController {
async register({ request }: HttpContext) {
try {
const data = await request.validateUsing(registerValidator)
const user = await User.create(data)
logger.info('User registered: %s', user.fullName, user.email, request.ip())
return User.accessTokens.create(user)
}
catch(error) {
logger.error('Registration failed: %s',error)
throw(error)
}
}
async login({ request }: HttpContext) {
try {
const { email, password } = await request.validateUsing(loginValidator)
const user = await User.verifyCredentials(email, password)
logger.info('User logged in: %s', email)
return User.accessTokens.create(user)
}
catch(error) {
logger.error('Login failed: %s',error)
throw error
}
}
async logout({ auth }: HttpContext) {
const user = auth.user!
logger.info('User logout: %s', auth.getUserOrFail().email)
try {
await User.accessTokens.delete(user, user.currentAccessToken.identifier)
return { message: 'User logged out' }
}
catch(error){
logger.error('Logout failed: %s',error)
throw(error)
}
}
async me({ auth }: HttpContext) {
if (await auth.check()) {
return { user: auth.user }
} else {
await User.accessTokens.delete(auth.user!, auth.user!.currentAccessToken.identifier)
return { message: 'Unauthorized' }
}
}
}
+28
View File
@@ -0,0 +1,28 @@
import app from '@adonisjs/core/services/app'
import { HttpContext, ExceptionHandler } from '@adonisjs/core/http'
export default class HttpExceptionHandler extends ExceptionHandler {
/**
* In debug mode, the exception handler will display verbose errors
* with pretty printed stack traces.
*/
protected debug = !app.inProduction
/**
* The method is used for handling errors and returning
* response to the client
*/
async handle(error: unknown, ctx: HttpContext) {
return super.handle(error, ctx)
}
/**
* The method is used to report error to the logging service or
* the third party error monitoring service.
*
* @note You should not attempt to send a response from this method.
*/
async report(error: unknown, ctx: HttpContext) {
return super.report(error, ctx)
}
}
+25
View File
@@ -0,0 +1,25 @@
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
import type { Authenticators } from '@adonisjs/auth/types'
/**
* Auth middleware is used authenticate HTTP requests and deny
* access to unauthenticated users.
*/
export default class AuthMiddleware {
/**
* The URL to redirect to, when authentication fails
*/
redirectTo = '/login'
async handle(
ctx: HttpContext,
next: NextFn,
options: {
guards?: (keyof Authenticators)[]
} = {}
) {
await ctx.auth.authenticateUsing(options.guards, { loginRoute: this.redirectTo })
return next()
}
}
@@ -0,0 +1,19 @@
import { Logger } from '@adonisjs/core/logger'
import { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
/**
* The container bindings middleware binds classes to their request
* specific value using the container resolver.
*
* - We bind "HttpContext" class to the "ctx" object
* - And bind "Logger" class to the "ctx.logger" object
*/
export default class ContainerBindingsMiddleware {
handle(ctx: HttpContext, next: NextFn) {
ctx.containerResolver.bindValue(HttpContext, ctx)
ctx.containerResolver.bindValue(Logger, ctx.logger)
return next()
}
}
@@ -0,0 +1,16 @@
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
/**
* Updating the "Accept" header to always accept "application/json" response
* from the server. This will force the internals of the framework like
* validator errors or auth errors to return a JSON response.
*/
export default class ForceJsonResponseMiddleware {
async handle({ request }: HttpContext, next: NextFn) {
const headers = request.headers()
headers.accept = 'application/json'
return next()
}
}
+13
View File
@@ -0,0 +1,13 @@
import { DateTime } from 'luxon'
import { BaseModel, column } from '@adonisjs/lucid/orm'
export default class RateLimit extends BaseModel {
@column({ isPrimary: true })
declare id: number
@column.dateTime({ autoCreate: true })
declare createdAt: DateTime
@column.dateTime({ autoCreate: true, autoUpdate: true })
declare updatedAt: DateTime
}
+13
View File
@@ -0,0 +1,13 @@
import { DateTime } from 'luxon'
import { BaseModel, column } from '@adonisjs/lucid/orm'
export default class RestaurantMenu extends BaseModel {
@column({ isPrimary: true })
declare id: number
@column.dateTime({ autoCreate: true })
declare createdAt: DateTime
@column.dateTime({ autoCreate: true, autoUpdate: true })
declare updatedAt: DateTime
}
+13
View File
@@ -0,0 +1,13 @@
import { DateTime } from 'luxon'
import { BaseModel, column } from '@adonisjs/lucid/orm'
export default class RestaurantMenuItem extends BaseModel {
@column({ isPrimary: true })
declare id: number
@column.dateTime({ autoCreate: true })
declare createdAt: DateTime
@column.dateTime({ autoCreate: true, autoUpdate: true })
declare updatedAt: DateTime
}
+39
View File
@@ -0,0 +1,39 @@
import { DateTime } from 'luxon'
import hash from '@adonisjs/core/services/hash'
import { compose } from '@adonisjs/core/helpers'
import { BaseModel, column } from '@adonisjs/lucid/orm'
import { withAuthFinder } from '@adonisjs/auth/mixins/lucid'
import { DbAccessTokensProvider } from '@adonisjs/auth/access_tokens'
const AuthFinder = withAuthFinder(() => hash.use('scrypt'), {
uids: ['email'],
passwordColumnName: 'password',
})
export default class User extends compose(BaseModel, AuthFinder) {
@column({ isPrimary: true })
declare id: number
@column()
declare fullName: string | null
@column()
declare email: string
@column({ serializeAs: null })
declare password: string
@column.dateTime({ autoCreate: true })
declare createdAt: DateTime
@column.dateTime({ autoCreate: true, autoUpdate: true })
declare updatedAt: DateTime | null
static accessTokens = DbAccessTokensProvider.forModel(User, {
expiresIn: '2 days',
prefix: 'tok_',
table: 'auth_access_tokens',
type: 'auth_token',
tokenSecretLength: 40,
})
}
+23
View File
@@ -0,0 +1,23 @@
import vine from '@vinejs/vine'
const password = vine.string().minLength(8)
export const registerValidator = vine.compile(
vine.object({
email: vine
.string()
.email()
.normalizeEmail()
.unique(async (db, value) => {
const match = await db.from('users').select('id').where('email', value).first()
return !match
}),
password,
})
)
export const loginValidator = vine.compile(
vine.object({
email: vine.string().email().normalizeEmail(),
password,
})
)