Skip to content
This repository has been archived by the owner on Mar 28, 2024. It is now read-only.

Commit

Permalink
feat(nano-server): get and require user auth
Browse files Browse the repository at this point in the history
BREAKING CHANGE: remove old auth methods
  • Loading branch information
njfamirm committed Dec 31, 2023
1 parent f8159ec commit fb3cb04
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 76 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
],
"cSpell.language": "en,fa,fa-IR",
"cSpell.words": [
"Alwatr"
"alwatr",
"nanoservice"
],
"typescript.tsdk": ".yarn/sdks/typescript/lib",
"git.autoStash": true,
Expand Down
112 changes: 39 additions & 73 deletions packages/nano-server/src/nano-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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';
Expand All @@ -33,6 +32,7 @@ export type {
AlwatrServiceResponseFailed,
AlwatrServiceResponseSuccess,
AlwatrServiceResponseSuccessWithMeta,
UserAuth,
};

definePackage('nano-server', '1.x');
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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<UserAuth> {
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<UserAuth>) => 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;
}

/**
Expand Down Expand Up @@ -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;
}
}
21 changes: 19 additions & 2 deletions packages/nano-server/src/type.ts
Original file line number Diff line number Diff line change
@@ -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;
}
Expand Down Expand Up @@ -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;
}

0 comments on commit fb3cb04

Please sign in to comment.