parent
36647a013d
commit
940d50b89c
@ -0,0 +1,41 @@
|
||||
import {SecurityContext} from './SecurityContext'
|
||||
import {AuthenticatableRepository} from '../types'
|
||||
import {Awaitable} from '../../util'
|
||||
import {Inject} from '../../di'
|
||||
import {Request} from '../../http/lifecycle/Request'
|
||||
import {OAuth2Token, TokenRepository} from '../server/types'
|
||||
import {UserAuthenticationResumedEvent} from '../event/UserAuthenticationResumedEvent'
|
||||
|
||||
export class TokenSecurityContext extends SecurityContext {
|
||||
@Inject()
|
||||
protected readonly request!: Request
|
||||
|
||||
@Inject()
|
||||
protected readonly tokens!: TokenRepository
|
||||
|
||||
constructor(
|
||||
public readonly repository: AuthenticatableRepository,
|
||||
) {
|
||||
super(repository, 'token')
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
persist(): Awaitable<void> {}
|
||||
|
||||
async resume(): Promise<void> {
|
||||
if ( !this.request.hasInstance(OAuth2Token) ) {
|
||||
return
|
||||
}
|
||||
|
||||
const token: OAuth2Token = this.request.getExistingInstance(OAuth2Token)
|
||||
if ( !token.userId ) {
|
||||
return
|
||||
}
|
||||
|
||||
const user = await this.repository.getByIdentifier(token.userId)
|
||||
if ( user ) {
|
||||
this.authenticatedUser = user
|
||||
await this.bus.push(new UserAuthenticationResumedEvent(user, this))
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
import {Middleware} from '../../http/routing/Middleware'
|
||||
import {ResponseObject} from '../../http/routing/Route'
|
||||
import {OAuth2Token} from '../server/types'
|
||||
import {HTTPError} from '../../http/HTTPError'
|
||||
import {HTTPStatus, Pipeline} from '../../util'
|
||||
import {Request} from '../../http/lifecycle/Request'
|
||||
import {Constructable, Container} from '../../di'
|
||||
|
||||
export class ScopeRequiredMiddleware extends Middleware {
|
||||
constructor(
|
||||
request: Request,
|
||||
protected readonly scope: string,
|
||||
) {
|
||||
super(request)
|
||||
}
|
||||
|
||||
apply(): ResponseObject {
|
||||
if ( !this.request.hasInstance(OAuth2Token) ) {
|
||||
throw new HTTPError(HTTPStatus.UNAUTHORIZED, 'Must specify an OAuth2 token.')
|
||||
}
|
||||
|
||||
const token: OAuth2Token = this.request.getExistingInstance(OAuth2Token)
|
||||
if ( typeof token.scope !== 'undefined' && token.scope !== this.scope ) {
|
||||
throw new HTTPError(HTTPStatus.UNAUTHORIZED, 'Insufficient token permissions (requires: ' + this.scope + ')')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const scope = (name: string): Constructable<ScopeRequiredMiddleware> => {
|
||||
return new Pipeline<Container, ScopeRequiredMiddleware>(
|
||||
container => container.make(ScopeRequiredMiddleware, name),
|
||||
)
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import {Middleware} from '../../http/routing/Middleware'
|
||||
import {Inject, Injectable} from '../../di'
|
||||
import {Config} from '../../service/Config'
|
||||
import {Logging} from '../../service/Logging'
|
||||
import {AuthenticatableRepository} from '../types'
|
||||
import {ResponseObject} from '../../http/routing/Route'
|
||||
import {SecurityContext} from '../context/SecurityContext'
|
||||
import {TokenSecurityContext} from '../context/TokenSecurityContext'
|
||||
import {OAuth2Token, oauth2TokenString, TokenRepository} from '../server/types'
|
||||
|
||||
/**
|
||||
* Injects a TokenSecurityContext into the request and attempts to
|
||||
* resume the user's authentication.
|
||||
*/
|
||||
@Injectable()
|
||||
export class TokenAuthMiddleware extends Middleware {
|
||||
@Inject()
|
||||
protected readonly config!: Config
|
||||
|
||||
@Inject()
|
||||
protected readonly logging!: Logging
|
||||
|
||||
@Inject()
|
||||
protected readonly tokens!: TokenRepository
|
||||
|
||||
async apply(): Promise<ResponseObject> {
|
||||
this.logging.debug('Applying token auth middleware.')
|
||||
let tokenString = this.request.getHeader('Authorization')
|
||||
if ( Array.isArray(tokenString) ) {
|
||||
tokenString = tokenString[0]
|
||||
}
|
||||
|
||||
if ( tokenString ) {
|
||||
const token = await this.tokens.decode(oauth2TokenString(tokenString))
|
||||
if ( token ) {
|
||||
this.request.registerSingletonInstance(OAuth2Token, token)
|
||||
}
|
||||
}
|
||||
|
||||
const repo = <AuthenticatableRepository> this.make(AuthenticatableRepository)
|
||||
const context = <TokenSecurityContext> this.make(TokenSecurityContext, repo)
|
||||
this.request.registerSingletonInstance(SecurityContext, context)
|
||||
await context.resume()
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
import {
|
||||
AbstractFactory,
|
||||
Container,
|
||||
DependencyRequirement,
|
||||
PropertyDependency,
|
||||
isInstantiable,
|
||||
DEPENDENCY_KEYS_METADATA_KEY,
|
||||
DEPENDENCY_KEYS_PROPERTY_METADATA_KEY, Instantiable, FactoryProducer,
|
||||
} from '../../di'
|
||||
import {Collection, ErrorWithContext} from '../../util'
|
||||
import {Config} from '../../service/Config'
|
||||
import {AuthenticatableRepository} from '../types'
|
||||
import {ORMUserRepository} from './orm/ORMUserRepository'
|
||||
|
||||
/**
|
||||
* A dependency injection factory that matches the abstract ClientRepository class
|
||||
* and produces an instance of the configured repository driver implementation.
|
||||
*/
|
||||
@FactoryProducer()
|
||||
export class AuthenticatableRepositoryFactory extends AbstractFactory<AuthenticatableRepository> {
|
||||
protected get config(): Config {
|
||||
return Container.getContainer().make<Config>(Config)
|
||||
}
|
||||
|
||||
produce(): AuthenticatableRepository {
|
||||
return new (this.getAuthenticatableRepositoryClass())()
|
||||
}
|
||||
|
||||
match(something: unknown): boolean {
|
||||
return something === AuthenticatableRepository
|
||||
}
|
||||
|
||||
getDependencyKeys(): Collection<DependencyRequirement> {
|
||||
const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getAuthenticatableRepositoryClass())
|
||||
if ( meta ) {
|
||||
return meta
|
||||
}
|
||||
return new Collection<DependencyRequirement>()
|
||||
}
|
||||
|
||||
getInjectedProperties(): Collection<PropertyDependency> {
|
||||
const meta = new Collection<PropertyDependency>()
|
||||
let currentToken = this.getAuthenticatableRepositoryClass()
|
||||
|
||||
do {
|
||||
const loadedMeta = Reflect.getMetadata(DEPENDENCY_KEYS_PROPERTY_METADATA_KEY, currentToken)
|
||||
if ( loadedMeta ) {
|
||||
meta.concat(loadedMeta)
|
||||
}
|
||||
currentToken = Object.getPrototypeOf(currentToken)
|
||||
} while (Object.getPrototypeOf(currentToken) !== Function.prototype && Object.getPrototypeOf(currentToken) !== Object.prototype)
|
||||
|
||||
return meta
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the instantiable class of the configured user repository backend.
|
||||
* @protected
|
||||
*/
|
||||
protected getAuthenticatableRepositoryClass(): Instantiable<AuthenticatableRepository> {
|
||||
const AuthenticatableRepositoryClass = this.config.get('auth.storage', ORMUserRepository)
|
||||
|
||||
if ( !isInstantiable(AuthenticatableRepositoryClass) || !(AuthenticatableRepositoryClass.prototype instanceof AuthenticatableRepository) ) {
|
||||
const e = new ErrorWithContext('Provided client repository class does not extend from @extollo/lib.AuthenticatableRepository')
|
||||
e.context = {
|
||||
configKey: 'auth.storage',
|
||||
class: AuthenticatableRepositoryClass.toString(),
|
||||
}
|
||||
}
|
||||
|
||||
return AuthenticatableRepositoryClass
|
||||
}
|
||||
}
|
Loading…
Reference in new issue