Start auth provider system
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
parent
cfd555723b
commit
506fb55c74
@ -0,0 +1,61 @@
|
|||||||
|
import {Request} from '../../http/lifecycle/Request'
|
||||||
|
import {ResponseObject, Route} from '../../http/routing/Route'
|
||||||
|
import {GuestRequiredMiddleware} from '../middleware/GuestRequiredMiddleware'
|
||||||
|
import {AuthRequiredMiddleware} from '../middleware/AuthRequiredMiddleware'
|
||||||
|
import {Inject, Injectable} from '../../di'
|
||||||
|
import {SecurityContext} from '../context/SecurityContext'
|
||||||
|
import {redirect} from '../../http/response/RedirectResponseFactory'
|
||||||
|
|
||||||
|
export interface LoginProviderConfig {
|
||||||
|
default: boolean,
|
||||||
|
allow?: {
|
||||||
|
login?: boolean,
|
||||||
|
registration?: boolean,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export abstract class LoginProvider<TConfig extends LoginProviderConfig> {
|
||||||
|
@Inject()
|
||||||
|
protected readonly security!: SecurityContext
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected name: string,
|
||||||
|
protected config: TConfig,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public routes(): void {
|
||||||
|
Route.get('login')
|
||||||
|
.alias(`@auth:${this.name}:login`)
|
||||||
|
.pipe(line => line.when(this.config.default, route => route.alias('@auth:login')))
|
||||||
|
.pre(GuestRequiredMiddleware)
|
||||||
|
.passingRequest()
|
||||||
|
.handledBy(this.login.bind(this))
|
||||||
|
|
||||||
|
Route.any('logout')
|
||||||
|
.alias(`@auth:${this.name}:logout`)
|
||||||
|
.pipe(line => line.when(this.config.default, route => route.alias('@auth:logout')))
|
||||||
|
.pre(AuthRequiredMiddleware)
|
||||||
|
.passingRequest()
|
||||||
|
.handledBy(this.logout.bind(this))
|
||||||
|
|
||||||
|
Route.get('register')
|
||||||
|
.alias(`@auth:${this.name}:register`)
|
||||||
|
.pipe(line => line.when(this.config.default, route => route.alias('@auth:register')))
|
||||||
|
.pre(GuestRequiredMiddleware)
|
||||||
|
.passingRequest()
|
||||||
|
.handledBy(this.registration.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract login(request: Request): ResponseObject
|
||||||
|
|
||||||
|
public abstract logout(request: Request): ResponseObject
|
||||||
|
|
||||||
|
public registration(request: Request): ResponseObject {
|
||||||
|
return this.login(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected redirectToIntendedRoute(): ResponseObject {
|
||||||
|
return redirect('/') // FIXME
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
export type BasicLoginAttempt = z.infer<typeof BasicLoginAttemptType>
|
||||||
|
|
||||||
|
export const BasicLoginAttemptType = z.object({
|
||||||
|
username: z.string().nonempty(),
|
||||||
|
password: z.string().nonempty(),
|
||||||
|
})
|
@ -0,0 +1,75 @@
|
|||||||
|
import {LoginProvider, LoginProviderConfig} from '../LoginProvider'
|
||||||
|
import {ResponseObject, Route} from '../../../http/routing/Route'
|
||||||
|
import {view} from '../../../http/response/ViewResponseFactory'
|
||||||
|
import {Valid, Validator} from '../../../validation/Validator'
|
||||||
|
import {BasicLoginAttempt, BasicLoginAttemptType} from './BasicLoginAttempt'
|
||||||
|
import {BasicRegistrationAttempt, BasicRegistrationAttemptType} from './BasicRegistrationAttempt'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LoginProvider implementation that provides basic username/password login.
|
||||||
|
*/
|
||||||
|
export class BasicLoginProvider extends LoginProvider<LoginProviderConfig> {
|
||||||
|
public routes(): void {
|
||||||
|
super.routes()
|
||||||
|
|
||||||
|
Route.post('/login')
|
||||||
|
.alias(`@auth:${this.name}:login.submit`)
|
||||||
|
.input(Validator.fromSchema<BasicLoginAttempt>(BasicLoginAttemptType))
|
||||||
|
.handledBy((...p) => this.attemptLogin(...p))
|
||||||
|
|
||||||
|
Route.post('/register')
|
||||||
|
.alias(`@auth:${this.name}:register.submit`)
|
||||||
|
.input(Validator.fromSchema<BasicRegistrationAttempt>(BasicRegistrationAttemptType))
|
||||||
|
.handledBy((...p) => this.attemptRegistration(...p))
|
||||||
|
}
|
||||||
|
|
||||||
|
public login(): ResponseObject {
|
||||||
|
return view('@extollo:auth:login')
|
||||||
|
}
|
||||||
|
|
||||||
|
public async logout(): Promise<ResponseObject> {
|
||||||
|
await this.security.flush()
|
||||||
|
return view('@extollo:auth:logout')
|
||||||
|
}
|
||||||
|
|
||||||
|
public registration(): ResponseObject {
|
||||||
|
return view('@extollo:auth:register')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Attempt to authenticate the user with a username/password. */
|
||||||
|
public async attemptLogin(attempt: Valid<BasicLoginAttempt>): Promise<ResponseObject> {
|
||||||
|
const user = await this.security.repository.getByIdentifier(attempt.username)
|
||||||
|
if ( !user ) {
|
||||||
|
throw new Error('TODO')
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !(await user.validateCredential(attempt.password)) ) {
|
||||||
|
throw new Error('TODO')
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.security.authenticate(user)
|
||||||
|
return this.redirectToIntendedRoute()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Attempt to register the user with a username/password. */
|
||||||
|
public async attemptRegistration(attempt: Valid<BasicRegistrationAttempt>): Promise<ResponseObject> {
|
||||||
|
const existingUser = await this.security.repository.getByIdentifier(attempt.username)
|
||||||
|
if ( existingUser ) {
|
||||||
|
throw new Error('TODO')
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( attempt.password !== attempt.passwordConfirmation ) {
|
||||||
|
throw new Error('TODO')
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await this.security.repository.createFromCredentials(attempt.username, attempt.password)
|
||||||
|
;(user as any).firstName = attempt.firstName
|
||||||
|
;(user as any).lastName = attempt.lastName
|
||||||
|
if ( typeof (user as any).save === 'function' ) {
|
||||||
|
await (user as any).save()
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.security.authenticate(user)
|
||||||
|
return this.redirectToIntendedRoute()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
export type BasicRegistrationAttempt = z.infer<typeof BasicRegistrationAttemptType>
|
||||||
|
|
||||||
|
export const BasicRegistrationAttemptType = z.object({
|
||||||
|
firstName: z.string().nonempty(),
|
||||||
|
|
||||||
|
lastName: z.string().nonempty(),
|
||||||
|
|
||||||
|
username: z.string().nonempty(),
|
||||||
|
|
||||||
|
password: z.string()
|
||||||
|
.nonempty()
|
||||||
|
.min(8),
|
||||||
|
|
||||||
|
passwordConfirmation: z.string()
|
||||||
|
.nonempty()
|
||||||
|
.min(8),
|
||||||
|
})
|
Loading…
Reference in new issue