diff --git a/.vscode/settings.json b/.vscode/settings.json index 8eb4571..1d7a9db 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -25,7 +25,8 @@ ], "cSpell.language": "en,fa,fa-IR", "cSpell.words": [ - "Alwatr" + "alwatr", + "nanoservice" ], "typescript.tsdk": ".yarn/sdks/typescript/lib", "git.autoStash": true, diff --git a/packages/nano-server/src/nano-server.ts b/packages/nano-server/src/nano-server.ts index 1172350..9e9ca49 100644 --- a/packages/nano-server/src/nano-server.ts +++ b/packages/nano-server/src/nano-server.ts @@ -3,7 +3,7 @@ import {createServer} from 'node:http'; import {createLogger, definePackage, type AlwatrLogger} from '@alwatr/logger'; import {isNumber} from '@alwatr/math'; -import type {NanoServerConfig, ConnectionConfig} from './type.js'; +import type {NanoServerConfig, ConnectionConfig, UserAuth} from './type'; import type { AlwatrServiceResponse, AlwatrServiceResponseFailed, @@ -16,7 +16,6 @@ import type { QueryParameters, Stringifyable, StringifyableRecord, - UserAuth, } from '@alwatr/type'; import type {IncomingMessage, ServerResponse} from 'node:http'; import type {Duplex} from 'node:stream'; @@ -33,6 +32,7 @@ export type { AlwatrServiceResponseFailed, AlwatrServiceResponseSuccess, AlwatrServiceResponseSuccessWithMeta, + UserAuth, }; definePackage('nano-server', '1.x'); @@ -398,19 +398,6 @@ export class AlwatrConnection { this._logger.logMethodArgs?.('constructor', {method: incomingMessage.method, url: incomingMessage.url}); } - /** - * Get the token placed in the request header. - */ - getAuthBearer(): string | null { - const auth = this.incomingMessage.headers.authorization?.split(' '); - - if (auth == null || auth[0].toLowerCase() !== 'bearer') { - return null; - } - - return auth[1]; - } - /** * Get request body for POST, PUT and POST methods. * @@ -482,64 +469,58 @@ export class AlwatrConnection { } /** - * Parse and validate request token. + * Get the user authentication information from the incoming message headers. * - * @returns Request token. + * @returns An object containing the user authentication information. * - * Example: + * @example * ```ts - * const token = connection.requireToken((token) => token.length > 12); - * if (token == null) return; + * const userAuth = connection.getUserAuth(); * ``` */ - requireToken(validator?: ((token: string) => boolean) | string[] | string): string { - const token = this.getAuthBearer(); - - if (token == null) { - throw { - ok: false, - statusCode: 401, - errorCode: 'authorization_required', - }; - } - else if (validator === undefined) { - return token; - } - else if (typeof validator === 'string') { - if (token === validator) return token; - } - else if (Array.isArray(validator)) { - if (validator.includes(token)) return token; - } - else if (typeof validator === 'function') { - if (validator(token) === true) return token; - } - throw { - ok: false, - statusCode: 403, - errorCode: 'access_denied', + getUserAuth(): Partial { + const userId = this.incomingMessage.headers['user-id']; + const userToken = this.incomingMessage.headers['user-token']; + const deviceId = this.incomingMessage.headers['device-id']; + + return { + id: userId, + token: userToken, + deviceId: deviceId, }; } /** - * Parse and get request user auth (include id and token). + * Get and validate the user authentication. * - * Example: + * @param validator Optional function to validate the user authentication. + * @returns The user authentication information. + * @throws {'access_denied'} If user authentication is missing or validation fails. + * + * @example * ```ts * const userAuth = connection.requireUserAuth(); * ``` */ - getUserAuth(): UserAuth | null { - const auth = this.getAuthBearer() - ?.split('/') - .filter((item) => item.trim() !== ''); - - return auth == null || auth.length !== 2 - ? null - : { - id: auth[0], - token: auth[1], + requireUserAuth(validator?: (userAuth: Partial) => boolean): UserAuth { + const userAuth = this.getUserAuth(); + + if (userAuth.id == null || userAuth.token == null || userAuth.deviceId == null) { + throw { + ok: false, + statusCode: 401, + errorCode: 'authorization_required', }; + } + else if (typeof validator === 'function' && validator(userAuth) !== true) { + throw { + ok: false, + statusCode: 403, + errorCode: 'access_denied', + }; + } + + return userAuth as UserAuth; } /** @@ -622,19 +603,4 @@ export class AlwatrConnection { 'unknown' ); } - - requireClientId(): string { - const clientId = this.incomingMessage.headers['client-id']; - - if (!clientId) { - // eslint-disable-next-line no-throw-literal - throw { - ok: false, - statusCode: 401, - errorCode: 'client_denied', - }; - } - - return clientId; - } } diff --git a/packages/nano-server/src/type.ts b/packages/nano-server/src/type.ts index 755a04f..9e3c4d6 100644 --- a/packages/nano-server/src/type.ts +++ b/packages/nano-server/src/type.ts @@ -1,9 +1,20 @@ declare module 'http' { interface IncomingHttpHeaders { /** - * Alwatr Client UUID + * Alwatr Device UUID */ - 'client-id'?: string; + 'device-id'?: string; + + /** + * User id + */ + 'user-id'?: string; + + /** + * User token + */ + 'user-token'?: string; + 'x-forwarded-for'?: string; } @@ -87,3 +98,9 @@ export interface ConnectionConfig { export type ParamKeyType = 'string' | 'number' | 'boolean'; export type ParamValueType = string | number | boolean | null; + +export interface UserAuth { + id: string; + token: string; + deviceId: string; +}