diff --git a/package.json b/package.json index 8556116..735e92d 100644 --- a/package.json +++ b/package.json @@ -28,5 +28,10 @@ "gulp": "^3.9.1", "gulp-typescript": "^3.1.5", "typescript": "^2.2.1" + }, + "dependencies": { + "inversify": "^3.1.0", + "randomstring": "^1.1.5", + "reflect-metadata": "^0.1.10" } } diff --git a/source/FlowFactory.ts b/source/FlowFactory.ts new file mode 100644 index 0000000..8f74771 --- /dev/null +++ b/source/FlowFactory.ts @@ -0,0 +1,48 @@ +import { IFlow } from './Flows/IFlow' + +type FlowTypeMap= { + type:string, + buildFunction:(data:any) => IFlow +}; + +/** + * Represents a factory that produce materialized instances of flows. + */ +class FlowFactory { + + private _flowTypeMapList:FlowTypeMap[]; + + /** + * Get a new flow instance builded with the given type and data. + * @param type Represents the type of flow to instantiate. + * @param data Represents the data to deserialize into the flow. + */ + public getFlow(type:string, data:any):IFlow { + + let map:FlowTypeMap = this._flowTypeMapList.find(function(map:FlowTypeMap) { return map.type == type }); + + if (!map) + throw new Error('The type [' + type + '] dont have any asociated build function in the factory.'); + + return map.buildFunction(data); + } + + /** + * Set the given build function as a builder for the given type. + * @param type Represents the type to assing the builder function. + * @param buildFunction Represents the function the build the flow of the given type. + */ + public setFlowTypeBuilder(type:string, buildFunction:(data:any) => IFlow):void { + + if (this._flowTypeMapList.find(function(map:FlowTypeMap) { return map.type == type })) + throw new Error('The type [' + type + '] already has a asociated build function in the factory.'); + + + this._flowTypeMapList.push({ + type: type + ,buildFunction: buildFunction + }); + } +} + +export = new FlowFactory(); diff --git a/source/FlowResultBuilder.ts b/source/FlowResultBuilder.ts new file mode 100644 index 0000000..ab793da --- /dev/null +++ b/source/FlowResultBuilder.ts @@ -0,0 +1,36 @@ +import { FlowResult } from "./Models/FlowResult"; +import { IFlowResultBuilder } from "./IFlowResultBuilder"; +import { ITokenService } from "./Services/ITokenService"; +import { Token } from "./Models/Persistents/Token"; + +/** + * Represents the a builder to vuild flows results. + */ +export class FlowResultBuilder implements IFlowResultBuilder { + + private _tokenService:ITokenService; + + /** + * Create a new instance of flow result builder. + * @param tokenService Represents a token service used to generate tokens. + */ + constructor(tokenService:ITokenService) { + + this._tokenService = tokenService; + } + + /** + * Return a builder FlowResult. + */ + public async getResult():Promise { + + let token:Token = await this._tokenService.generateToken(); + let result:FlowResult = new FlowResult(); + + result.accessToken = token.value; + result.expireIn = token.expireIn; + result.refreshToken = token.refreshToken ? token.refreshToken.value : null; + + return result; + } +} \ No newline at end of file diff --git a/source/Flows/AuthorizationCodeFlow.ts b/source/Flows/AuthorizationCodeFlow.ts new file mode 100644 index 0000000..6002f07 --- /dev/null +++ b/source/Flows/AuthorizationCodeFlow.ts @@ -0,0 +1,30 @@ +import { ScopeBaseFlow } from './ScopeBaseFlow' +import { IFlowHandler } from '../IFlowHandler' +import { IClientService } from "../Services/IClientService"; +import { IFlowResultBuilder } from "../IFlowResultBuilder"; + +/** + * Represents a authorization code flow. + */ +export class AuthorizationCodeFlow extends ScopeBaseFlow { + + constructor(clientService:IClientService + ,flowResultBuilder:IFlowResultBuilder) { + + super('authorization_code', clientService); + } + + /** + * Execute the flow resolution and handle it with the given handler. + * @param handle Represents the handler. + */ + public async resolve(handler:IFlowHandler):Promise { + + if (!super.resolve(handler) && await this._userService.validateCredentials(this.username, this.password)) { + handler.returnResult(await this._flowResultBuilder.getResult()); + } + else + return false; + + } +} \ No newline at end of file diff --git a/source/Flows/BaseFlow.ts b/source/Flows/BaseFlow.ts new file mode 100644 index 0000000..987903a --- /dev/null +++ b/source/Flows/BaseFlow.ts @@ -0,0 +1,72 @@ +import { IFlow } from './IFlow' +import { IFlowHandler } from '../IFlowHandler' +import { FlowError } from "../Models/FlowError"; +import { IClientService } from "../Services/IClientService"; +import { OAuthError } from "../OAuthError"; + +/** + * Represents a base class for the oAuth flows. + */ +export abstract class BaseFlow implements IFlow { + /** + * Construct a new base flow [BaseFP] with the given type. + * @param type Represents the type of the flow processor. + */ + constructor(type:string, clientService:IClientService) { + this._type = type; + } + + private _type:string; + + /** + * Get the oAuth flow type. + */ + get type() { return this._type;} + + /** + * Get or set the client id with which the flow get the result. + */ + public clientId: string; + + /** + * Execute the flow resolution and handle it with the given handler. + * @param handle Represents the handler. + * @returns A boolean value specifying if the flow is reolved. + */ + public async resolve(handler:IFlowHandler):Promise { + + let error:FlowError = await this.validate(); + + if (error) { + handler.notifyError(error); + return true; // I resolve it; + } + else + return false; // I can not resolve it; + }; + + /** + * Execute the error resolution and handle it with the given handler. + * @param error Represents the error to resolve. + * @param handler Represents the handler. + * @returns A boolean value specifying if the error is reolved. + */ + protected resolveError(error:Error, handler:IFlowHandler) { + if (error instanceof OAuthError) { + + let oauthError:OAuthError = error; + + handler.notifyError(new FlowError(oauthError.code, oauthError.message)); + } + else + return false; + } + + /** + * Return a flow error instance when a error is founded in the evaluated flow. + * If return null no error was found. + */ + protected async validate():Promise { + return null; + }; +} \ No newline at end of file diff --git a/source/Flows/ClientCredetialFlow.ts b/source/Flows/ClientCredetialFlow.ts new file mode 100644 index 0000000..788d77b --- /dev/null +++ b/source/Flows/ClientCredetialFlow.ts @@ -0,0 +1,55 @@ +import { BaseFlow } from './BaseFlow' +import { IFlowHandler } from '../IFlowHandler' +import { IClientService } from "../Services/IClientService"; +import { FlowResult } from "../Models/FlowResult"; +import { Token } from "../Models/Persistents/Token"; +import { ITokenService } from "../Services/ITokenService"; +import { IFlowResultBuilder } from "../IFlowResultBuilder"; + +/** + * Represents a client credential flow. + */ +export class ClientCredetialFlow extends BaseFlow { + + private _clientService:IClientService; + private _flowResultBuilder:IFlowResultBuilder; + + /** + * Create ne instance of client credential flow. + * @param clientService Represents a client service to authenticate and validate the client. + * @param flowResultBuilder Represents a builder to build results. + */ + constructor(clientService:IClientService + ,flowResultBuilder:IFlowResultBuilder) { + + super('client_credential', clientService); + + this._clientService = clientService; + this._flowResultBuilder = flowResultBuilder; + } + + /** + * Get or set the client secret for the flow authentication. + */ + public clientSecret:string; + + /** + * Execute the flow resolution and handle it with the given handler. + * @param handle Represents the handler. + */ + public async resolve(handler:IFlowHandler):Promise { + + if (!super.resolve(handler)) { + try { + await this._clientService.validateCredentials(this.clientId, this.clientSecret); + handler.returnResult(await this._flowResultBuilder.getResult()); + } + catch(ex) { + if (!this.resolveError(ex, handler)) + throw ex; + } + } + else + return true; + } +} \ No newline at end of file diff --git a/source/Flows/CodeExchangeFlow.ts b/source/Flows/CodeExchangeFlow.ts new file mode 100644 index 0000000..f1c769d --- /dev/null +++ b/source/Flows/CodeExchangeFlow.ts @@ -0,0 +1,35 @@ +import { BaseFlow } from './BaseFlow' +import { IFlowHandler } from '../IFlowHandler' + +/** + * Represents a code exchange flow. + */ +export class CodeExchangeFlow extends BaseFlow { + constructor() { + super('code_exchange'); + } + + /** + * Get or set the code generated by the authorization code flow. + */ + public code:string; + + /** + * Get or set the redirection url used in the authorization code flow. + */ + public redirectUri:string; + + /** + * Get or set the client secret for the flow authentication. + */ + public clientSecret:string; + + /** + * Execute the flow and handle it with the given handler. + * @param handler Represents the handler for the flow. + */ + public execute(handler:IFlowHandler):void { + + + } +} \ No newline at end of file diff --git a/source/Flows/IFlow.ts b/source/Flows/IFlow.ts new file mode 100644 index 0000000..d4a3a57 --- /dev/null +++ b/source/Flows/IFlow.ts @@ -0,0 +1,12 @@ +import { IFlowHandler } from '../IFlowHandler' + +/** + * Represents a oAuth flow. + */ +export interface IFlow { + /** + * Execute the flow resolution and handle it with the given handler. + * @param handle Represents the handler. + */ + resolve(handler:IFlowHandler):Promise; +} \ No newline at end of file diff --git a/source/Flows/IFlowResultBuilder.ts b/source/Flows/IFlowResultBuilder.ts new file mode 100644 index 0000000..ee0893e --- /dev/null +++ b/source/Flows/IFlowResultBuilder.ts @@ -0,0 +1,11 @@ +import { FlowResult } from "./Models/FlowResult"; + +/** + * Represents the a builder to vuild flows results. + */ +export interface IFlowResultBuilder { + /** + * Return a builder FlowResult. + */ + getResult():Promise +} \ No newline at end of file diff --git a/source/Flows/ImplicitFlow.ts b/source/Flows/ImplicitFlow.ts new file mode 100644 index 0000000..4a3ae0c --- /dev/null +++ b/source/Flows/ImplicitFlow.ts @@ -0,0 +1,19 @@ +import { ScopeBaseFlow } from './ScopeBaseFlow' +import { IFlowHandler } from '../IFlowHandler' + +/** + * Represents a implicit flow. + */ +export class AuthorizationCodeFlow extends ScopeBaseFlow { + constructor() { + super('implicit'); + } + + /** + * Execute the flow and handle it with the given handler. + * @param handler Represents the handler for the flow. + */ + public execute(handler:IFlowHandler):void { + + } +} \ No newline at end of file diff --git a/source/Flows/PasswordFlow.ts b/source/Flows/PasswordFlow.ts new file mode 100644 index 0000000..614322c --- /dev/null +++ b/source/Flows/PasswordFlow.ts @@ -0,0 +1,67 @@ +import { BaseFlow } from './BaseFlow' +import { FlowResult } from '../Models/FlowResult' +import { IFlowHandler } from '../IFlowHandler' +import { Token } from "../Models/Persistents/Token"; +import { ITokenService } from "../Services/ITokenService"; +import { IUserService } from "../Services/IUserService"; +import { FlowError } from "../Models/FlowError"; +import { IFlowResultBuilder } from "../IFlowResultBuilder"; +import { IClientService } from "../Services/IClientService"; +import { OAuthError } from "../OAuthError"; + + +/** + * Represents the password flow. + */ +export class PasswordFlow extends BaseFlow { + + private _userService:IUserService; + private _clientService:IClientService; + private _flowResultBuilder:IFlowResultBuilder; + + /** + * Create a new instance of password flow class. + * @param userService Represents a user service used to authenticate the user. + * @param clientService Represents a client service to perform some basic flow validations. + * @param flowResultBuilder Represents a builder to build results. + */ + constructor(userService:IUserService + ,clientService:IClientService + ,flowResultBuilder:IFlowResultBuilder) { + + super('password', clientService); + + this._userService = userService; + this._clientService = clientService; + } + + /** + * Represents the user name to authenticate; + */ + public username:string; + /** + * Represents the password of the user name to authenticate; + */ + public password:string; + + /** + * Execute the flow resolution and handle it with the given handler. + * @param handle Represents the handler. + */ + public async resolve(handler:IFlowHandler):Promise { + + if (!super.resolve(handler)) { + try { + await this._userService.validateCredentials(this.username, this.password); + handler.returnResult(await this._flowResultBuilder.getResult()); + } + catch(ex) { + if (!this.resolveError(ex, handler)) + throw ex; + } + } + else + return true; + + } +} \ No newline at end of file diff --git a/source/Flows/ScopeBaseFlow.ts b/source/Flows/ScopeBaseFlow.ts new file mode 100644 index 0000000..71ced8e --- /dev/null +++ b/source/Flows/ScopeBaseFlow.ts @@ -0,0 +1,61 @@ +import { BaseFlow } from './BaseFlow' +import { IFlowHandler } from '../IFlowHandler' +import { IClientService } from "../Services/IClientService"; +import { FlowError } from "../Models/FlowError"; + +/** + * Represents a base class for scoped based oAuth flows. + */ +export abstract class ScopeBaseFlow extends BaseFlow { + constructor(type:string, clientService:IClientService) { + super(type, clientService); + } + + /** + * Get or set the state to validate the authenticity when is compared with the result state. + */ + public state:string; + + /** + * Get or set the redirection url used in the authorization code flow. + */ + public redirectUri:string; + + /** + * Get or set the requested scopes. + */ + public scopes:string[]; + + /** + * Execute the flow resolution and handle it with the given handler. + * @param handle Represents the handler. + */ + public async resolve(handler:IFlowHandler):Promise { + + if (!super.resolve(handler)) + { + let error:FlowError = await this.validate(); + + if (error) { + handler.notifyError(error); + return true; // I resolve it; + } + } + + return false; // I can not resolve it; + }; + + /** + * Return a flow error instance when a error is founded in the evaluated flow. + * If return null no error was found. + */ + private async validate():Promise { + + let error:FlowError = await super.validate(); + + if (error) + return error; + + return null; + }; +} \ No newline at end of file diff --git a/source/IFlowHandler.ts b/source/IFlowHandler.ts new file mode 100644 index 0000000..d2df008 --- /dev/null +++ b/source/IFlowHandler.ts @@ -0,0 +1,23 @@ +import { FlowResult } from './Models/FlowResult' +import { FlowError } from "./Models/FlowError"; + +/** + * Represents a handler for the flows. + */ +export interface IFlowHandler { + /** + * Redirect the request to another page, usually the authorization page. + * @param url Represents the url to redirect. + */ + redirectToURL(url:string):void + /** + * Return the flow result to the requester client. + * @param result Represents the result to return. + */ + returnResult(result:FlowResult):void + /** + * Notify an error that may occur. + * @param Represents the error that occur. + */ + notifyError(error:FlowError):void +} \ No newline at end of file diff --git a/source/IFlowResultBuilder.ts b/source/IFlowResultBuilder.ts new file mode 100644 index 0000000..ee0893e --- /dev/null +++ b/source/IFlowResultBuilder.ts @@ -0,0 +1,11 @@ +import { FlowResult } from "./Models/FlowResult"; + +/** + * Represents the a builder to vuild flows results. + */ +export interface IFlowResultBuilder { + /** + * Return a builder FlowResult. + */ + getResult():Promise +} \ No newline at end of file diff --git a/source/Models/FlowError.ts b/source/Models/FlowError.ts new file mode 100644 index 0000000..11b0162 --- /dev/null +++ b/source/Models/FlowError.ts @@ -0,0 +1,28 @@ +/** + * Represents a error in a flow validation. + */ +export class FlowError { + + private _code:string; + private _message:string; + + /** + * Create a new flow error instance with the given code and message. + * @param code + * @param message + */ + constructor(code:string, message:string) { + this._code = code; + this._message = message; + } + + /** + * Get the error code. + */ + public get code() { return this._code; } + + /** + * Get the error message. + */ + public get message() { return this._message }; +} \ No newline at end of file diff --git a/source/Models/FlowResult.ts b/source/Models/FlowResult.ts new file mode 100644 index 0000000..47cf552 --- /dev/null +++ b/source/Models/FlowResult.ts @@ -0,0 +1,21 @@ +/** + * Represents a result of a direct flow execution. + */ +export class FlowResult { + /** + * Represents the access token generated by the flow excecution. + */ + public accessToken:string; + /** + * Represents the refresh token generated by the flow excecution. + */ + public refreshToken:string; + /** + *Represents the time in which the access token will expire. + */ + public expireIn:number; + /** + * Represents an state to validate the authenticity of the response result. + */ + public state:string; +} \ No newline at end of file diff --git a/source/Models/Persistents/AuthorizationCode.ts b/source/Models/Persistents/AuthorizationCode.ts new file mode 100644 index 0000000..b140735 --- /dev/null +++ b/source/Models/Persistents/AuthorizationCode.ts @@ -0,0 +1,32 @@ +import { Client } from "./Client"; +import { Token } from "./Token"; + +/** + * Represents an authorization code. + */ +export class AuthorizationCode { + /** + * Get or set the code. + */ + public code:string; + + /** + * Get or set the client that generate the code. + */ + public client:Client; + + /** + * Get or set the state of the code. + */ + public state:string; + + /** + * Get or set the token generated with the coded; + */ + public token:Token; + + /** + * Get or set the date when the code was created. + */ + public creationDate:Date; +} \ No newline at end of file diff --git a/source/Models/Persistents/BaseToken.ts b/source/Models/Persistents/BaseToken.ts new file mode 100644 index 0000000..f27c84c --- /dev/null +++ b/source/Models/Persistents/BaseToken.ts @@ -0,0 +1,20 @@ +/** + * Represents a base token. + */ +export abstract class BaseToken { + + /** + * Get or set the value of the token. + */ + public value:string; + + /** + * The time in seconds to the token expiration. + */ + public expireIn:number; + + /** + * Get or set the creation date. + */ + public creationDate:Date; +} \ No newline at end of file diff --git a/source/Models/Persistents/Client.ts b/source/Models/Persistents/Client.ts new file mode 100644 index 0000000..c08f780 --- /dev/null +++ b/source/Models/Persistents/Client.ts @@ -0,0 +1,19 @@ +/** + * Represents a client. + */ +export class Client { + /** + * Get or set the client id. + */ + public clientId:string; + + /** + * Get or set the client secret. + */ + public clientSecret:string; + + /** + * Get or set the redirect uri fro the client. + */ + public redirectURI:string; +} \ No newline at end of file diff --git a/source/Models/Persistents/RefreshToken.ts b/source/Models/Persistents/RefreshToken.ts new file mode 100644 index 0000000..ae53b57 --- /dev/null +++ b/source/Models/Persistents/RefreshToken.ts @@ -0,0 +1,8 @@ +import { BaseToken } from './BaseToken' + +/** + * Represents a refresh token. + */ +export class RefreshToken extends BaseToken { + +} \ No newline at end of file diff --git a/source/Models/Persistents/Token.ts b/source/Models/Persistents/Token.ts new file mode 100644 index 0000000..26aca86 --- /dev/null +++ b/source/Models/Persistents/Token.ts @@ -0,0 +1,18 @@ +import { BaseToken } from './BaseToken' +import { Client } from "./Client"; + +/** + * Represents a token. + */ +export class Token extends BaseToken { + + /** + * Get or set the client that generate the token; + */ + public client:Client; + + /** + * Get or set the refresh token information. + */ + public refreshToken:BaseToken; +} \ No newline at end of file diff --git a/source/Models/Persistents/User.ts b/source/Models/Persistents/User.ts new file mode 100644 index 0000000..7beb4d4 --- /dev/null +++ b/source/Models/Persistents/User.ts @@ -0,0 +1,15 @@ +/** + * Represents an user. + */ +export class User { + + /** + * Get or set the user name. + */ + public name:string; + + /** + * Get or set the user password. + */ + public password:string; +} \ No newline at end of file diff --git a/source/OAuthContext.ts b/source/OAuthContext.ts new file mode 100644 index 0000000..da94065 --- /dev/null +++ b/source/OAuthContext.ts @@ -0,0 +1,31 @@ +import { OAuthContextConfiguration } from './OAuthContextConfiguration' + +/** + * Represents an OAuth context. + */ +export class OAuthContext { + + private _configuration:OAuthContextConfiguration; + + /** + * Create a new instance of the OAuth context with a default configuration. + */ + constructor() { + this.loadConfiguration(); + } + + /** + * Get the context configuration. + */ + public get configuration() { return this._configuration; } + + private loadConfiguration() { + this._configuration = new OAuthContextConfiguration(); + this._configuration.tokenExpirationTime = null; + this._configuration.refreshTokenExpirationTime = null; + this._configuration.authorizationCodeExpirationTime = 30; + this._configuration.mustGenerateRefreshToken = false; + this._configuration.tokenLength = 30; + this._configuration.authorizationCodeLength = 10; + } +} \ No newline at end of file diff --git a/source/OAuthContextConfiguration.ts b/source/OAuthContextConfiguration.ts new file mode 100644 index 0000000..f2b2880 --- /dev/null +++ b/source/OAuthContextConfiguration.ts @@ -0,0 +1,35 @@ +/** + * Represents a set of configurations for an OAuth context; + */ +export class OAuthContextConfiguration { + + /** + * Get or set the amount of time in second for a token to expire; + */ + public tokenExpirationTime:number; + + /** + * Get or set the amount of time in second for a refresh token to expire; + */ + public refreshTokenExpirationTime:number; + + /** + * Get or set the amount of time in second for an authorization code to expire; + */ + public authorizationCodeExpirationTime:number; + + /** + * Get or set a boolean value specifying if the token generation process must create an refresh token; + */ + public mustGenerateRefreshToken:boolean; + + /** + * Get or set the length of the generated tokens. + */ + public tokenLength:number; + + /** + * Get or set the length of the generated authorization codes. + */ + public authorizationCodeLength:number; +} \ No newline at end of file diff --git a/source/OAuthError.ts b/source/OAuthError.ts new file mode 100644 index 0000000..4c4310c --- /dev/null +++ b/source/OAuthError.ts @@ -0,0 +1,23 @@ +/** + * Represents an OAuth error. + */ +export class OAuthError extends Error { + + private _code:string; + + /** + * Create a new instance of OAuth error. + * @param code Represents the code of the error. + * @param message Represents the message of the error. + */ + constructor(code:string, message:string) { + super(message); + + this._code = code; + } + + /** + * Get the code of the error. + */ + public get code() { return this._code; }; +} \ No newline at end of file diff --git a/source/Repositories/IAuthorizationCodeRepository.ts b/source/Repositories/IAuthorizationCodeRepository.ts new file mode 100644 index 0000000..0c88855 --- /dev/null +++ b/source/Repositories/IAuthorizationCodeRepository.ts @@ -0,0 +1,19 @@ +import { AuthorizationCode } from '../Models/Persistents/AuthorizationCode' + +/** + * Represents a authorization code repository to persists authorization codes. + */ +export interface IAuthorizationCodeRepository { + + /** + * Returns an authorization code by the given code. + * @param code Represents the code to look for. + */ + getByCode(code:string):Promise; + + /** + * Save or update authorization code instances. + * @param authorization Represents the authorization code instance that will be saved or updated. + */ + save(authorizationClient:AuthorizationCode):Promise; +} \ No newline at end of file diff --git a/source/Repositories/IClientRepository.ts b/source/Repositories/IClientRepository.ts new file mode 100644 index 0000000..4ce4d7b --- /dev/null +++ b/source/Repositories/IClientRepository.ts @@ -0,0 +1,12 @@ +import { Client } from '../Models/Persistents/Client' + +/** + * Represents a client repository to persists clients. + */ +export interface IClientRepository { + /** + * Return a client instance by the given client id. + * @param clientId Represents the id of the client to look for. + */ + getById(clientId:string):Promise; +} \ No newline at end of file diff --git a/source/Repositories/ITokenRepository.ts b/source/Repositories/ITokenRepository.ts new file mode 100644 index 0000000..fd441c6 --- /dev/null +++ b/source/Repositories/ITokenRepository.ts @@ -0,0 +1,19 @@ +import { Token } from '../Models/Persistents/Token' + +/** + * Represents a token repository to persists tokens. + */ +export interface ITokenRepository { + + /** + * Returns a tokent instance by the given value. + * @param value Represents the value to look for. + */ + getByValue(value:string):Promise; + + /** + * Save or update the token instance; + * @param token The token instance to persist. + */ + save(token:Token):Promise; +} \ No newline at end of file diff --git a/source/Repositories/IUserRepository.ts b/source/Repositories/IUserRepository.ts new file mode 100644 index 0000000..2c2f8b5 --- /dev/null +++ b/source/Repositories/IUserRepository.ts @@ -0,0 +1,13 @@ +import { User } from '../Models/Persistents/User' + +/** + * Represents an user repository to persists users. + */ +export interface IUserRepository { + + /** + * Returns an user instance by a given name. + * @param username Represent the user name to look for. + */ + getByName(username:string):Promise; +} \ No newline at end of file diff --git a/source/Services/AuthorizationCodeService.ts b/source/Services/AuthorizationCodeService.ts new file mode 100644 index 0000000..694530f --- /dev/null +++ b/source/Services/AuthorizationCodeService.ts @@ -0,0 +1,109 @@ +import { Client } from "../Models/Persistents/Client"; +import { AuthorizationCode } from "../Models/Persistents/AuthorizationCode"; +import { IAuthorizationCodeService } from "./IAuthorizationCodeService"; +import { IAuthorizationCodeRepository } from "../Repositories/IAuthorizationCodeRepository"; +import { ITokenRepository } from "../Repositories/ITokenRepository"; +import { Token } from "../Models/Persistents/Token"; +import { OAuthError } from "../OAuthError"; +import { IClientService } from "./IClientService"; +import { IClientRepository } from "../Repositories/IClientRepository"; +import { IIdentifierGenerationService } from "./IIdentifierGenerationService"; + +/** + * Represents an authorization code service that can generate and validate codes. + */ +export class AuthorizationCodeService implements IAuthorizationCodeService { + + private _authorizarionCodeRepository:IAuthorizationCodeRepository; + private _tokenRepository:ITokenRepository; + private _clientService:IClientService; + private _clientRepository:IClientRepository; + private _identifierGenerationService:IIdentifierGenerationService; + + /** + * Creates a new instance of authorization code service. + * @param authorizarionCodeRepository Represents a repository to persist and consult authorization codes. + * @param tokenRepository Represents a repository to persist and consult tokens. + * @param clientService Represents a client service to validate the clients. + * @param clientRepository Represents a repository to persist and consult clients. + * @param identifierGenerationService Represents a identifier generation service to generate codes. + */ + constructor(authorizarionCodeRepository:IAuthorizationCodeRepository + ,tokenRepository:ITokenRepository + ,clientService:IClientService + ,clientRepository:IClientRepository + ,identifierGenerationService:IIdentifierGenerationService) { + + this._authorizarionCodeRepository = authorizarionCodeRepository; + this._tokenRepository = tokenRepository; + this._clientService = clientService; + this._clientRepository = clientRepository; + this._identifierGenerationService = identifierGenerationService; + } + + /** + * Validate if the given code is valid based on the given client. + * @param code Represents the code to validate. + * @param client Represents a client instance to validate the code. + */ + public async validateCode(code:string, client:Client):Promise { + + let authorizationCode:AuthorizationCode = await this._authorizarionCodeRepository.getByCode(code); + + if (!authorizationCode) + throw new OAuthError('TODO:PutCode', 'The provided authorization code do not exists'); + + if (!client) + throw new OAuthError('TODO:PutCode', 'A client must be provided.'); + + this._clientService.validateCredentials(client.clientId, client.clientSecret); + + if (client.clientId != authorizationCode.client.clientId) + throw new OAuthError('TODO:PutCode', 'The client is unauthorized to exhange the provided code.'); + } + + /** + * Returns an authorization code instance by the given code. + * @param clientId Represents the clientId that own the code. + * @param state Represents the state of the code. + */ + public async generateCode(clientId:string, state:string = null):Promise { + + let authorizationCode:AuthorizationCode = new AuthorizationCode(); + let client:Client = await this._clientRepository.getById(clientId); + + if (!client) + throw new OAuthError('TODO:PutCode', 'There is not a client with the provided client Id'); + + authorizationCode.code = await this._identifierGenerationService.getAuthorizationCodeValue(); + authorizationCode.client = client; + authorizationCode.creationDate = new Date(); + authorizationCode.state = state; + + await this._authorizarionCodeRepository.save(authorizationCode); + + return authorizationCode; + } + + /** + * Associate a token to an existing authorization code. + * @param code Represents the code of the authorization code which the token will be associated. + * @param token Represents the token that will be associated to the authorization code. + */ + public async associateToken(code: string, token: string): Promise { + + let authorizationCode:AuthorizationCode = await this._authorizarionCodeRepository.getByCode(code); + + if (!authorizationCode) + throw new OAuthError('TODO:PutCode', 'The provided authorization code do not exists'); + + let tokenInstance:Token = await this._tokenRepository.getByValue(token); + + if (!tokenInstance) + throw new Error('The provided token don exists, the association can not be performed.'); + + authorizationCode.token = tokenInstance; + + this._authorizarionCodeRepository.save(authorizationCode); + } +} \ No newline at end of file diff --git a/source/Services/ClientService.ts b/source/Services/ClientService.ts new file mode 100644 index 0000000..d12f805 --- /dev/null +++ b/source/Services/ClientService.ts @@ -0,0 +1,34 @@ +import { IClientRepository } from "../Repositories/IClientRepository"; +import { Client } from "../Models/Persistents/Client"; +import { IClientService } from "./IClientService"; +import { OAuthError } from "../OAuthError"; + +/** + * Represents a client service. + */ +export class ClientService implements IClientService { + + private _clientRepository:IClientRepository; + + /** + * Create a new instance of client service. + * @param clientRepository Represents a client repository to consult clients. + */ + constructor(clientRepository:IClientRepository) { + this._clientRepository = clientRepository; + } + + /** + * Validate the credentials of the client. + * @param clientId Represents the id of the client to authenticate. + * @param clientSecret Represents the secret of the client to authenticate. + */ + public async validateCredentials(clientId:string, clientSecret:string):Promise { + + let client:Client = await this._clientRepository.getById(clientId); + + if (client && client.clientSecret === clientSecret) { + throw new OAuthError('TODO:PutCode', 'The provided client credentials are invalid.'); + }; + } +} \ No newline at end of file diff --git a/source/Services/IAuthorizationCodeService.ts b/source/Services/IAuthorizationCodeService.ts new file mode 100644 index 0000000..7b64d35 --- /dev/null +++ b/source/Services/IAuthorizationCodeService.ts @@ -0,0 +1,29 @@ +import { Client } from "../Models/Persistents/Client"; +import { AuthorizationCode } from "../Models/Persistents/AuthorizationCode"; + +/** + * Represents an authorization code service that can generate and validate codes. + */ +export interface IAuthorizationCodeService { + + /** + * Validate if the given code is valid based on the given client. + * @param code Represents the code to validate. + * @param client Represents a client instance to validate the code. + */ + validateCode(code:string, client:Client):Promise; + + /** + * Returns an authorization code instance by the given code. + * @param clientId Represents the clientId that own the code. + * @param state Represents the state of the code. + */ + generateCode(clientId:string, state:string):Promise; + + /** + * Associate a token to an existing authorization code. + * @param code Represents the code of the authorization code which the token will be associated. + * @param token Represents the token that will be associated to the authorization code. + */ + associateToken(code:string, token:string):Promise; +} \ No newline at end of file diff --git a/source/Services/IClientService.ts b/source/Services/IClientService.ts new file mode 100644 index 0000000..36e8f63 --- /dev/null +++ b/source/Services/IClientService.ts @@ -0,0 +1,13 @@ +/** + * Represents a client service. + */ +import { Client } from "../Models/Persistents/Client"; + +export interface IClientService { + /** + * Validate the credentials of the client. + * @param clientId Represents the id of the client to authenticate. + * @param clientSecret Represents the secret of the client to authenticate. + */ + validateCredentials(clientId:string, clientSecret:string):Promise; +} \ No newline at end of file diff --git a/source/Services/IIdentifierGenerationService.ts b/source/Services/IIdentifierGenerationService.ts new file mode 100644 index 0000000..b93d461 --- /dev/null +++ b/source/Services/IIdentifierGenerationService.ts @@ -0,0 +1,15 @@ +/** + * Represents a token generation service to generate tokens. + */ +export interface IIdentifierGenerationService { + + /** + * Returns a new ramdom token value; + */ + getTokenValue():Promise; + + /** + * Returns a new ramdom authorization code value; + */ + getAuthorizationCodeValue():Promise; +} \ No newline at end of file diff --git a/source/Services/ITokenService.ts b/source/Services/ITokenService.ts new file mode 100644 index 0000000..adda849 --- /dev/null +++ b/source/Services/ITokenService.ts @@ -0,0 +1,18 @@ +import { Token } from '../Models/Persistents/Token' + +/** + * Represents a token service. + */ +export interface ITokenService { + + /** + * Returns a token intance by the given value. + * @param tokenValue Represents the token value to look for. + */ + getByValue(tokenValue:string):Promise; + + /** + * Returns a new generated token. + */ + generateToken():Promise; +} \ No newline at end of file diff --git a/source/Services/IUserService.ts b/source/Services/IUserService.ts new file mode 100644 index 0000000..521d725 --- /dev/null +++ b/source/Services/IUserService.ts @@ -0,0 +1,11 @@ +/** + * Represent an user service. + */ +export interface IUserService { + /** + * Validate the credentials of the user. + * @param username Represents the user name. + * @param password Represents the user password. + */ + validateCredentials(username:string, password:string):Promise; +} \ No newline at end of file diff --git a/source/Services/IdentifierGenerationService.ts b/source/Services/IdentifierGenerationService.ts new file mode 100644 index 0000000..f0ca892 --- /dev/null +++ b/source/Services/IdentifierGenerationService.ts @@ -0,0 +1,33 @@ +import randomstring = require("randomstring") +import { OAuthContext } from "../OAuthContext"; +import { IIdentifierGenerationService } from "./IIdentifierGenerationService"; + +/** + * Represents a identifier generation service to generate tokens and authorization codes. + */ +export class IdentifierGenerationService implements IIdentifierGenerationService { + + private _oAuthContext:OAuthContext; + + /** + * Create a new instance of the token generation service. + * @param oAuthContext Represents the OAuth context that containing the default configurations for common objects. + */ + constructor(oAuthContext:OAuthContext) { + this._oAuthContext = oAuthContext; + } + + /** + * Returns a new ramdom token value; + */ + public async getTokenValue():Promise { + return randomstring.generate(this._oAuthContext.configuration.tokenLength | 30); + } + + /** + * Returns a new ramdom authorization code value; + */ + public async getAuthorizationCodeValue(): Promise { + return randomstring.generate(this._oAuthContext.configuration.authorizationCodeLength | 10); + } +} \ No newline at end of file diff --git a/source/Services/TokenService.ts b/source/Services/TokenService.ts new file mode 100644 index 0000000..330c130 --- /dev/null +++ b/source/Services/TokenService.ts @@ -0,0 +1,59 @@ +import { Token } from '../Models/Persistents/Token' +import { ITokenService } from './ITokenService' +import { ITokenRepository } from '../Repositories/ITokenRepository' +import { OAuthContext } from '../OAuthContext' +import { RefreshToken } from "../Models/Persistents/RefreshToken"; +import { IIdentifierGenerationService } from './IIdentifierGenerationService' + +/** + * Represents a token service. + */ +export class TokenService implements ITokenService { + + private _tokenRepository:ITokenRepository; + private _tokenGenerationService:IIdentifierGenerationService; + private _oAuthContext:OAuthContext; + + /** + * Create a new instance of the token service. + * @param tokenRepository Represents the token repository to consult and persist tokens. + * @param tokenGenerationService Represents the token generation service to generate tokens values. + * @param oAuthContext Represents the OAuth context that containing the default configurations for common objects. + */ + constructor(tokenRepository:ITokenRepository + ,tokenGenerationService:IIdentifierGenerationService + ,oAuthContext:OAuthContext) { + + this._tokenRepository = tokenRepository; + this._tokenGenerationService = tokenGenerationService; + this._oAuthContext = oAuthContext; + } + + /** + * Returns a token intance by the given value. + * @param tokenValue Represents the token value to look for. + */ + public async getByValue(tokenValue:string):Promise { + return await this._tokenRepository.getByValue(tokenValue); + } + + /** + * Returns a new generated token. + */ + public async generateToken():Promise { + + let newToken:Token = new Token(); + newToken.value = await this._tokenGenerationService.getTokenValue(); + newToken.expireIn = this._oAuthContext.configuration.tokenExpirationTime; + + if (this._oAuthContext.configuration.mustGenerateRefreshToken) { + newToken.refreshToken = new RefreshToken(); + newToken.refreshToken.value = await this._tokenGenerationService.getTokenValue(); + newToken.refreshToken.expireIn = this._oAuthContext.configuration.refreshTokenExpirationTime; + } + + await this._tokenRepository.save(newToken); + + return newToken; + } +} \ No newline at end of file diff --git a/source/Services/UserService.ts b/source/Services/UserService.ts new file mode 100644 index 0000000..2a8d664 --- /dev/null +++ b/source/Services/UserService.ts @@ -0,0 +1,34 @@ +import { User } from '../Models/Persistents/User' +import { IUserService } from './IUserService' +import { IUserRepository } from '../Repositories/IUserRepository' +import { OAuthError } from "../OAuthError"; + +/** + * Represent an user service. + */ +export class UserService implements IUserService { + + private _userRepository:IUserRepository; + + /** + * Create a new instance of the user service with tha given repository. + * @param userRepository Represet the repository to interact with the data. + */ + constructor(userRepository:IUserRepository) { + this._userRepository = userRepository; + } + + /** + * Validate the credentials of the user. + * @param username Represents the user name. + * @param password Represents the user password. + */ + public async validateCredentials(username: string, password: string): Promise { + + let user:User = await this._userRepository.getByName(username); + + if (user && user.password === password) { + throw new OAuthError('TODO:PutCode', 'The provided user credentials are invalid.'); + } + } +} \ No newline at end of file diff --git a/source/main.ts b/source/main.ts index a13c6fa..fd40910 100644 --- a/source/main.ts +++ b/source/main.ts @@ -1,3 +1,4 @@ -export class Main { - -} \ No newline at end of file + + + +