123 lines
3.6 KiB
TypeScript
123 lines
3.6 KiB
TypeScript
import {Inject, Injectable} from '../../di'
|
|
import {Awaitable, HTTPStatus, Maybe} from '../../util'
|
|
import {Authenticatable, AuthenticatableRepository} from '../types'
|
|
import {Logging} from '../../service/Logging'
|
|
import {UserAuthenticatedEvent} from '../event/UserAuthenticatedEvent'
|
|
import {UserFlushedEvent} from '../event/UserFlushedEvent'
|
|
import {Bus} from '../../support/bus'
|
|
import {HTTPError} from '../../http/HTTPError'
|
|
|
|
/**
|
|
* Base-class for a context that authenticates users and manages security.
|
|
*/
|
|
@Injectable()
|
|
export abstract class SecurityContext {
|
|
@Inject()
|
|
protected readonly bus!: Bus
|
|
|
|
@Inject()
|
|
protected readonly logging!: Logging
|
|
|
|
/** The currently authenticated user, if one exists. */
|
|
protected authenticatedUser?: Authenticatable
|
|
|
|
constructor(
|
|
/** The repository where users are persisted. */
|
|
public readonly repository: AuthenticatableRepository,
|
|
|
|
/** The name of this context. */
|
|
public readonly name: string,
|
|
) { }
|
|
|
|
/**
|
|
* Called when the context is created. Can be used by child-classes to do setup work.
|
|
*/
|
|
initialize(): Awaitable<void> {} // eslint-disable-line @typescript-eslint/no-empty-function
|
|
|
|
/**
|
|
* Authenticate the given user, without persisting the authentication.
|
|
* That is, when the lifecycle ends, the user will be unauthenticated implicitly.
|
|
* @param user
|
|
*/
|
|
async authenticateOnce(user: Authenticatable): Promise<void> {
|
|
this.authenticatedUser = user
|
|
await this.bus.push(new UserAuthenticatedEvent(user, this))
|
|
}
|
|
|
|
/**
|
|
* Authenticate the given user and persist the authentication.
|
|
* @param user
|
|
*/
|
|
async authenticate(user: Authenticatable): Promise<void> {
|
|
this.authenticatedUser = user
|
|
await this.persist()
|
|
await this.bus.push(new UserAuthenticatedEvent(user, this))
|
|
}
|
|
|
|
/**
|
|
* Unauthenticate the current user, if one exists, but do not persist the change.
|
|
*/
|
|
async flushOnce(): Promise<void> {
|
|
const user = this.authenticatedUser
|
|
if ( user ) {
|
|
this.authenticatedUser = undefined
|
|
await this.bus.push(new UserFlushedEvent(user, this))
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unauthenticate the current user, if one exists, and persist the change.
|
|
*/
|
|
async flush(): Promise<void> {
|
|
const user = this.authenticatedUser
|
|
if ( user ) {
|
|
this.authenticatedUser = undefined
|
|
await this.persist()
|
|
await this.bus.push(new UserFlushedEvent(user, this))
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Assuming a user is still authenticated in the context,
|
|
* try to look up and fill in the user.
|
|
*
|
|
* If there is NO USER to be resumed, then the method should flush
|
|
* the user from this context.
|
|
*/
|
|
abstract resume(): Awaitable<void>
|
|
|
|
/**
|
|
* Write the current state of the security context to whatever storage
|
|
* medium the context's host provides.
|
|
*/
|
|
abstract persist(): Awaitable<void>
|
|
|
|
/**
|
|
* Get the currently authenticated user, if one exists.
|
|
*/
|
|
getUser(): Maybe<Authenticatable> {
|
|
return this.authenticatedUser
|
|
}
|
|
|
|
/** Get the current user or throw an authorization error. */
|
|
user(): Authenticatable {
|
|
if ( !this.hasUser() ) {
|
|
throw new HTTPError(HTTPStatus.UNAUTHORIZED)
|
|
}
|
|
|
|
const user = this.getUser()
|
|
if ( !user ) {
|
|
throw new HTTPError(HTTPStatus.UNAUTHORIZED)
|
|
}
|
|
|
|
return user
|
|
}
|
|
|
|
/**
|
|
* Returns true if there is a currently authenticated user.
|
|
*/
|
|
hasUser(): boolean {
|
|
return Boolean(this.authenticatedUser)
|
|
}
|
|
}
|