Add middleware and logic for bootstrapping the session auth
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
91abcdf8ef
commit
f00233d49a
@ -0,0 +1,11 @@
|
|||||||
|
import {HTTPError} from '../http/HTTPError'
|
||||||
|
import {HTTPStatus} from '../util'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error thrown when a user attempts an action that they are not authorized to perform.
|
||||||
|
*/
|
||||||
|
export class NotAuthorizedError extends HTTPError {
|
||||||
|
constructor(message = 'Not Authorized') {
|
||||||
|
super(HTTPStatus.FORBIDDEN, message)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import {Instantiable} from '../di'
|
||||||
|
import {ORMUserRepository} from './orm/ORMUserRepository'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inferface for type-checking the AuthenticatableRepositories values.
|
||||||
|
*/
|
||||||
|
export interface AuthenticatableRepositoryMapping {
|
||||||
|
orm: Instantiable<ORMUserRepository>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String mapping of AuthenticatableRepository implementations.
|
||||||
|
*/
|
||||||
|
export const AuthenticatableRepositories: AuthenticatableRepositoryMapping = {
|
||||||
|
orm: ORMUserRepository,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for making the auth config type-safe.
|
||||||
|
*/
|
||||||
|
export interface AuthConfig {
|
||||||
|
repositories: {
|
||||||
|
session: keyof AuthenticatableRepositoryMapping,
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
import {Event} from '../../event/Event'
|
||||||
|
import {SecurityContext} from '../SecurityContext'
|
||||||
|
import {Awaitable, JSONState} from '../../util'
|
||||||
|
import {Authenticatable} from '../types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event fired when a security context for a given user is resumed.
|
||||||
|
*/
|
||||||
|
export class UserAuthenticationResumedEvent extends Event {
|
||||||
|
constructor(
|
||||||
|
public readonly user: Authenticatable,
|
||||||
|
public readonly context: SecurityContext,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
async dehydrate(): Promise<JSONState> {
|
||||||
|
return {
|
||||||
|
user: await this.user.dehydrate(),
|
||||||
|
contextName: this.context.name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rehydrate(state: JSONState): Awaitable<void> { // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
|
// TODO fill this in
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,21 @@
|
|||||||
export * from './types'
|
export * from './types'
|
||||||
|
export * from './NotAuthorizedError'
|
||||||
|
|
||||||
export * from './SecurityContext'
|
export * from './SecurityContext'
|
||||||
|
|
||||||
export * from './event/UserAuthenticatedEvent'
|
export * from './event/UserAuthenticatedEvent'
|
||||||
export * from './event/UserFlushedEvent'
|
export * from './event/UserFlushedEvent'
|
||||||
|
export * from './event/UserAuthenticationResumedEvent'
|
||||||
|
|
||||||
export * from './contexts/SessionSecurityContext'
|
export * from './contexts/SessionSecurityContext'
|
||||||
|
|
||||||
export * from './orm/ORMUser'
|
export * from './orm/ORMUser'
|
||||||
export * from './orm/ORMUserRepository'
|
export * from './orm/ORMUserRepository'
|
||||||
|
|
||||||
|
export * from './middleware/AuthRequiredMiddleware'
|
||||||
|
export * from './middleware/GuestRequiredMiddleware'
|
||||||
|
export * from './middleware/SessionAuthMiddleware'
|
||||||
|
|
||||||
export * from './Authentication'
|
export * from './Authentication'
|
||||||
|
|
||||||
|
export * from './config'
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
import {Middleware} from '../../http/routing/Middleware'
|
||||||
|
import {Inject, Injectable} from '../../di'
|
||||||
|
import {SecurityContext} from '../SecurityContext'
|
||||||
|
import {ResponseObject} from '../../http/routing/Route'
|
||||||
|
import {error} from '../../http/response/ErrorResponseFactory'
|
||||||
|
import {NotAuthorizedError} from '../NotAuthorizedError'
|
||||||
|
import {HTTPStatus} from '../../util'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuthRequiredMiddleware extends Middleware {
|
||||||
|
@Inject()
|
||||||
|
protected readonly security!: SecurityContext
|
||||||
|
|
||||||
|
async apply(): Promise<ResponseObject> {
|
||||||
|
if ( !this.security.hasUser() ) {
|
||||||
|
return error(new NotAuthorizedError(), HTTPStatus.FORBIDDEN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import {Middleware} from '../../http/routing/Middleware'
|
||||||
|
import {Inject, Injectable} from '../../di'
|
||||||
|
import {SecurityContext} from '../SecurityContext'
|
||||||
|
import {ResponseObject} from '../../http/routing/Route'
|
||||||
|
import {error} from '../../http/response/ErrorResponseFactory'
|
||||||
|
import {NotAuthorizedError} from '../NotAuthorizedError'
|
||||||
|
import {HTTPStatus} from '../../util'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GuestRequiredMiddleware extends Middleware {
|
||||||
|
@Inject()
|
||||||
|
protected readonly security!: SecurityContext
|
||||||
|
|
||||||
|
async apply(): Promise<ResponseObject> {
|
||||||
|
if ( this.security.hasUser() ) {
|
||||||
|
return error(new NotAuthorizedError(), HTTPStatus.FORBIDDEN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
import {Middleware} from '../../http/routing/Middleware'
|
||||||
|
import {Inject, Injectable} from '../../di'
|
||||||
|
import {ResponseObject} from '../../http/routing/Route'
|
||||||
|
import {Config} from '../../service/Config'
|
||||||
|
import {AuthenticatableRepository} from '../types'
|
||||||
|
import {SessionSecurityContext} from '../contexts/SessionSecurityContext'
|
||||||
|
import {SecurityContext} from '../SecurityContext'
|
||||||
|
import {ORMUserRepository} from '../orm/ORMUserRepository'
|
||||||
|
import {AuthConfig} from '../config'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injects a SessionSecurityContext into the request and attempts to
|
||||||
|
* resume the user's authentication.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class SessionAuthMiddleware extends Middleware {
|
||||||
|
@Inject()
|
||||||
|
protected readonly config!: Config
|
||||||
|
|
||||||
|
async apply(): Promise<ResponseObject> {
|
||||||
|
const context = <SessionSecurityContext> this.make(SessionSecurityContext, this.getRepository())
|
||||||
|
this.request.registerSingletonInstance(SecurityContext, context)
|
||||||
|
await context.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the correct AuthenticatableRepository based on the auth config.
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected getRepository(): AuthenticatableRepository {
|
||||||
|
const config: AuthConfig | undefined = this.config.get('auth')
|
||||||
|
return this.make<AuthenticatableRepository>(config?.repositories?.session ?? ORMUserRepository)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue