Merge mintaka lib

This commit is contained in:
2024-03-06 19:39:31 -03:00
parent b4d074420a
commit c6136b6e21
15 changed files with 747 additions and 23 deletions

12
examples/http.js Normal file
View File

@ -0,0 +1,12 @@
// Copyright (c) Tribufu. All Rights Reserved.
// SPDX-License-Identifier: MIT
import { HttpClient } from '../build/index.mjs';
async function main() {
const http = new HttpClient();
const reponse = await http.get('https://www.tribufu.com');
console.log(reponse);
}
main();

View File

@ -16,14 +16,25 @@
"build": "npm run clean && tsc && node scripts/esbuild.js"
},
"dependencies": {
"@tribufu/mintaka": "0.1.8"
"axios": "^1.6.3",
"camelcase-keys": "^9.1.2",
"fp-ts": "^2.16.1",
"json-bigint": "^1.0.0",
"jsonwebtoken": "^9.0.2",
"snakecase-keys": "^5.5.0",
"toml": "^3.0.0",
"uuid": "^9.0.1",
"uuidv7": "^0.6.3"
},
"devDependencies": {
"@types/json-bigint": "^1.0.4",
"@types/jsonwebtoken": "^9.0.5",
"@types/node": "^20.10.6",
"@types/uuid": "^9.0.7",
"cross-env": "^7.0.3",
"dotenv": "^16.3.1",
"esbuild": "^0.19.10",
"esbuild-node-externals": "^1.12.0",
"esbuild": "^0.19.10",
"rimraf": "^5.0.5",
"typescript": "^5.3.3"
}

65
pnpm-lock.yaml generated
View File

@ -5,14 +5,47 @@ settings:
excludeLinksFromLockfile: false
dependencies:
'@tribufu/mintaka':
specifier: 0.1.8
version: 0.1.8
axios:
specifier: ^1.6.3
version: 1.6.4
camelcase-keys:
specifier: ^9.1.2
version: 9.1.2
fp-ts:
specifier: ^2.16.1
version: 2.16.2
json-bigint:
specifier: ^1.0.0
version: 1.0.0
jsonwebtoken:
specifier: ^9.0.2
version: 9.0.2
snakecase-keys:
specifier: ^5.5.0
version: 5.5.0
toml:
specifier: ^3.0.0
version: 3.0.0
uuid:
specifier: ^9.0.1
version: 9.0.1
uuidv7:
specifier: ^0.6.3
version: 0.6.3
devDependencies:
'@types/json-bigint':
specifier: ^1.0.4
version: 1.0.4
'@types/jsonwebtoken':
specifier: ^9.0.5
version: 9.0.6
'@types/node':
specifier: ^20.10.6
version: 20.10.6
'@types/uuid':
specifier: ^9.0.7
version: 9.0.8
cross-env:
specifier: ^7.0.3
version: 7.0.3
@ -260,21 +293,15 @@ packages:
dev: true
optional: true
/@tribufu/mintaka@0.1.8:
resolution: {integrity: sha512-qUvReWlz8irSIbKCTfjFnUnUq7MIgLjnTBPeWv2ayyiwGkf8L3q7qi1Zxuqt3OduOugSOtxYQwXOaClldIMUTQ==}
/@types/json-bigint@1.0.4:
resolution: {integrity: sha512-ydHooXLbOmxBbubnA7Eh+RpBzuaIiQjh8WGJYQB50JFGFrdxW7JzVlyEV7fAXw0T2sqJ1ysTneJbiyNLqZRAag==}
dev: true
/@types/jsonwebtoken@9.0.6:
resolution: {integrity: sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==}
dependencies:
axios: 1.6.4
camelcase-keys: 9.1.2
fp-ts: 2.16.2
json-bigint: 1.0.0
jsonwebtoken: 9.0.2
snakecase-keys: 5.5.0
toml: 3.0.0
uuid: 9.0.1
uuidv7: 0.6.3
transitivePeerDependencies:
- debug
dev: false
'@types/node': 20.10.6
dev: true
/@types/node@20.10.6:
resolution: {integrity: sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==}
@ -282,6 +309,10 @@ packages:
undici-types: 5.26.5
dev: true
/@types/uuid@9.0.8:
resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==}
dev: true
/ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}

View File

@ -1,9 +1,11 @@
// Copyright (c) Tribufu. All Rights Reserved.
// SPDX-License-Identifier: MIT
import { HttpHeaders, HttpClient } from "@tribufu/mintaka";
import { HttpClient } from "./http/client";
import { HttpHeaders } from "./http/headers";
import { JavaScriptRuntime } from "./node";
import { JsonCasing, JwtDecoder } from "@tribufu/mintaka";
import { JwtDecoder } from "./jwt";
import { JsonCasing } from "./json";
import { TokenPayload } from "./token";
import { TRIBUFU_API_URL, TRIBUFU_VERSION } from ".";
import { TribufuApiOptions } from "./options";

View File

@ -1,8 +1,9 @@
// Copyright (c) Tribufu. All Rights Reserved.
// SPDX-License-Identifier: MIT
import { HttpCookieMap, HttpHeaders } from "@tribufu/mintaka";
import { OAuth2GrantType, OAuth2IntrospectionRequest, OAuth2IntrospectionResponse, OAuth2TokenRequest, OAuth2TokenResponse } from "@tribufu/mintaka";
import { HttpHeaders } from "./http/headers";
import { HttpCookieMap } from "./http/cookies";
import { OAuth2GrantType, OAuth2IntrospectionRequest, OAuth2IntrospectionResponse, OAuth2TokenRequest, OAuth2TokenResponse } from "./oauth2";
import { TribufuApi } from "./api";
/**

View File

@ -0,0 +1,42 @@
// Copyright (c) Tribufu. All Rights Reserved.
// SPDX-License-Identifier: MIT
/**
* Map Inner
*
* Helper type to represent raw map.
*/
export type RawStringMap<T> = {
[key: string]: T
};
/**
* Generic Map
*
* Helper class to manage generic maps.
*/
export class StringMap<T> extends Map<string, T> {
constructor(inner?: RawStringMap<T> | null) {
super();
if (inner) {
Object.entries(inner).forEach(([key, value]) => {
this.set(key, value);
});
}
}
/**
* Get all values as raw map.
* @returns {RawStringMap<T>}
*/
public getRaw(): RawStringMap<T> {
const result: RawStringMap<T> = {};
this.forEach((value, key) => {
result[key] = value;
});
return result;
}
}

199
src/http/client.ts Normal file
View File

@ -0,0 +1,199 @@
// Copyright (c) Tribufu. All Rights Reserved.
// SPDX-License-Identifier: MIT
import axios, { AxiosInstance } from "axios";
import { JsonCasing, JsonSerializer } from "../json";
import { HttpHeaders } from "./headers";
export interface ErrorResponse {
error: string;
};
export interface MessageResponse {
message: string;
};
export interface HttpClientOptions {
baseUrl?: string | null;
headers?: HttpHeaders;
logEnabled?: boolean;
logTarget?: string;
jsonRequestCasing?: JsonCasing | null;
jsonResponseCasing?: JsonCasing | null;
};
/**
* Http Client
*
* Helper class to make HTTP requests.
*/
export class HttpClient {
private readonly inner: AxiosInstance;
protected readonly options: HttpClientOptions;
constructor(options?: HttpClientOptions | null) {
const defaultOptions = HttpClient.defaultOptions();
this.options = {
baseUrl: options?.baseUrl ?? defaultOptions.baseUrl,
headers: options?.headers ?? defaultOptions.headers,
logEnabled: options?.logEnabled ?? defaultOptions.logEnabled,
logTarget: options?.logTarget ?? defaultOptions.logTarget,
jsonRequestCasing: options?.jsonRequestCasing ?? defaultOptions.jsonRequestCasing,
jsonResponseCasing: options?.jsonResponseCasing ?? defaultOptions.jsonResponseCasing,
};
const inner = axios.create({
baseURL: this.options?.baseUrl ?? undefined,
headers: this.options?.headers?.getRaw(),
});
inner.interceptors.request.use((req) => {
if (this.options.logEnabled ?? false) {
console.log(`(${this.options.logTarget}) ${req.method?.toUpperCase()} ${req.baseURL}${req.url}`);
}
if (req.url && req.url.includes("oauth2") && !req.url.includes("userinfo")) {
return req;
}
const contentType = req.headers["Content-Type"];
if (req.data && (contentType === "application/json" || contentType === "application/x-www-form-urlencoded")) {
req.data = JsonSerializer.toCase(req.data, this.options.jsonRequestCasing);
}
return req;
});
inner.interceptors.response.use((res) => {
if (res.config.url && res.config.url.includes("oauth2") && !res.config.url.includes("userinfo")) {
return res;
}
if (res.data) {
res.data = JsonSerializer.toCase(res.data, this.options.jsonResponseCasing);
}
return res;
});
this.inner = inner;
}
private static defaultOptions(): HttpClientOptions {
return {
baseUrl: null,
headers: new HttpHeaders(),
logEnabled: false,
logTarget: "HttpClient",
jsonRequestCasing: null,
jsonResponseCasing: null,
};
};
/**
* Get a resource from the http server.
* @returns {T | null}
*/
public async get<T>(path: string, headers?: HttpHeaders | null): Promise<T | null> {
try {
const requestHeaders = headers ?? this.options.headers;
const response = await this.inner.get(path, { headers: requestHeaders?.getRaw() });
if (response.status !== 200) {
return null;
}
return response.data as T;
} catch (error) {
return null;
}
}
/**
* Create a resource on the http server.
* @param path
* @param body
* @param headers
* @returns {T | null}
*/
public async post<S, T>(path: string, body: S, headers?: HttpHeaders | null): Promise<T | null> {
try {
const requestHeaders = headers ?? this.options.headers;
const response = await this.inner.post(path, body, { headers: requestHeaders?.getRaw() });
if (response.status !== 200) {
return null;
}
return response.data as T;
} catch (error) {
return null;
}
}
/**
* Update a resource on the http server.
* @param path
* @param body
* @param headers
* @returns {T | null}
*/
public async put<S, T>(path: string, body: S, headers?: HttpHeaders | null): Promise<T | null> {
try {
const requestHeaders = headers ?? this.options.headers;
const response = await this.inner.put(path, body, { headers: requestHeaders?.getRaw() });
if (response.status !== 200) {
return null;
}
return response.data as T;
} catch (error) {
return null;
}
}
/**
* Patch a resource on the http server.
* @param path
* @param body
* @param headers
* @returns {T | null}
*/
public async patch<S, T>(path: string, body: S, headers?: HttpHeaders | null): Promise<T | null> {
try {
const requestHeaders = headers ?? this.options.headers;
const response = await this.inner.patch(path, body, { headers: requestHeaders?.getRaw() });
if (response.status !== 200) {
return null;
}
return response.data as T;
} catch (error) {
return null;
}
}
/**
* Delete a resource from the http server.
* @param path
* @param headers
* @returns {T | null}
*/
public async delete<T>(path: string, headers?: HttpHeaders | null): Promise<T | null> {
try {
const requestHeaders = headers ?? this.options.headers;
const response = await this.inner.delete(path, { headers: requestHeaders?.getRaw() });
if (response.status !== 200) {
return null;
}
return response.data as T;
} catch (error) {
return null;
}
}
}

19
src/http/cookies.ts Normal file
View File

@ -0,0 +1,19 @@
// Copyright (c) Tribufu. All Rights Reserved.
// SPDX-License-Identifier: MIT
import { RawStringMap, StringMap } from "../collections/string_map";
/**
* Http Cookie Map
*
* Helper type to represent HTTP cookies.
*/
export type HttpCookieMap = RawStringMap<string>;
/**
* Http Cookies
*
* Helper class to manage HTTP cookies.
*/
export class HttpCookies extends StringMap<string> {
}

28
src/http/headers.ts Normal file
View File

@ -0,0 +1,28 @@
// Copyright (c) Tribufu. All Rights Reserved.
// SPDX-License-Identifier: MIT
import { RawStringMap, StringMap } from "../collections/string_map";
/**
* Http Header Map
*
* Helper type to represent HTTP headers.
*/
export type HttpHeaderMap = RawStringMap<string>;
/**
* Http Headers
*
* Helper class to manage HTTP headers.
*/
export class HttpHeaders extends StringMap<string> {
public static parseAuthorizationHeader(value: string): { scheme: string, token: string } {
const parts = value.split(" ");
if (parts.length !== 2) {
throw new Error("Invalid authorization header");
}
return { scheme: parts[0], token: parts[1] };
}
}

View File

@ -28,3 +28,107 @@ export {
TribufuClient,
TribufuServer,
};
import {
RawStringMap,
StringMap,
} from "./collections/string_map";
export {
RawStringMap,
StringMap,
}
import {
HttpClient,
HttpClientOptions,
} from "./http/client";
export {
HttpClient,
HttpClientOptions,
}
import {
HttpCookieMap,
HttpCookies,
} from "./http/cookies";
export {
HttpCookieMap,
HttpCookies,
}
import {
HttpHeaderMap,
HttpHeaders,
} from "./http/headers";
export {
HttpHeaderMap,
HttpHeaders,
}
import {
UuidGenerator,
} from "./uuid";
export {
UuidGenerator,
}
import {
JwtDecoder,
} from "./jwt";
export {
JwtDecoder,
}
import {
JsonCasing,
JsonSerializer,
} from "./json";
export {
JsonCasing,
JsonSerializer,
}
import {
TomlSerializer,
} from "./toml";
export {
TomlSerializer,
}
import {
OAuth2AuthorizeRequest,
OAuth2ClientType,
OAuth2CodeResponse,
OAuth2GrantType,
OAuth2IntrospectionRequest,
OAuth2IntrospectionResponse,
OAuth2ResponseType,
OAuth2RevokeRequest,
OAuth2TokenHintType,
OAuth2TokenRequest,
OAuth2TokenResponse,
OAuth2TokenType
} from "./oauth2";
export {
OAuth2AuthorizeRequest,
OAuth2ClientType,
OAuth2CodeResponse,
OAuth2GrantType,
OAuth2IntrospectionRequest,
OAuth2IntrospectionResponse,
OAuth2ResponseType,
OAuth2RevokeRequest,
OAuth2TokenHintType,
OAuth2TokenRequest,
OAuth2TokenResponse,
OAuth2TokenType
}

92
src/json/index.ts Normal file
View File

@ -0,0 +1,92 @@
// Copyright (c) Tribufu. All Rights Reserved.
// SPDX-License-Identifier: MIT
import camelcaseKeys from "camelcase-keys";
import snakecaseKeys from "snakecase-keys";
export enum JsonCasing {
CamelCase,
PascalCase,
SnakeCase,
};
export class JsonSerializer {
/**
* Format json to string.
*
* @param json
* @returns {string}
*/
public static toString(object: any): string {
return JSON.stringify(object, null, 0);
}
/**
* Format json to pretty string.
*
* @param json
* @returns {string}
*/
public static toStringPretty(object: any): string {
return JSON.stringify(object, null, 4);
}
/**
* Parse json string to object.
* @param json
* @returns {any}
*/
public static fromString(jsonString: string): any {
return JSON.parse(jsonString);
}
/**
* Convert json object keys to camel case.
*
* @param json
* @returns {any}
*/
public static toCamelCase(json: any): any {
return camelcaseKeys(json, { deep: true });
}
/**
* Convert json object keys to pascal case.
*
* @param json
* @returns {any}
*/
public static toPascalCase(json: any): any {
return camelcaseKeys(json, { deep: true, pascalCase: true });
}
/**
* Convert json object keys to snake case.
*
* @param json
* @returns {any}
*/
public static toSnakeCase(json: any): any {
return snakecaseKeys(json, { deep: true });
}
/**
* Convert json object keys to specified case.
*
* @param json
* @param casing
* @returns {any}
*/
public static toCase(json: any, casing?: JsonCasing | null): any {
switch (casing) {
case JsonCasing.CamelCase:
return JsonSerializer.toCamelCase(json);
case JsonCasing.PascalCase:
return JsonSerializer.toPascalCase(json);
case JsonCasing.SnakeCase:
return JsonSerializer.toSnakeCase(json);
default:
return json;
}
}
}

36
src/jwt/index.ts Normal file
View File

@ -0,0 +1,36 @@
// Copyright (c) Tribufu. All Rights Reserved.
// SPDX-License-Identifier: MIT
import jwt from "jsonwebtoken";
export class JwtDecoder {
/**
* Decode JWT token.
*
* @param token
* @returns {any}
*/
public static decode(token: string): any {
return jwt.decode(token);
}
/**
* Encode JWT token.
*
* @param token
* @returns {any}
*/
public static encode(payload: any, secret: string, options?: any): string {
return jwt.sign(payload, secret, options);
}
/**
* Verify JWT token.
*
* @param token
* @returns {any}
*/
public static verify(token: string, secret: string): any {
return jwt.verify(token, secret);
}
}

101
src/oauth2/index.ts Normal file
View File

@ -0,0 +1,101 @@
// Copyright (c) Tribufu. All Rights Reserved.
// SPDX-License-Identifier: MIT
/**
* Helper type to represent OAuth2 client type.
*/
export type OAuth2ClientType = "web" | "native";
/**
* Helper type to represent OAuth2 grant type.
*/
export type OAuth2GrantType = "authorization_code" | "client_credentials" | "device_code" | "password" | "passkey" | "refresh_token";
/**
* Helper type to represent OAuth2 response type.
*/
export type OAuth2ResponseType = "code" | "token";
/**
* Helper type to represent OAuth2 token hint type.
*/
export type OAuth2TokenHintType = "refresh_token" | "access_token";
/**
* Helper type to represent OAuth2 token type.
*/
export type OAuth2TokenType = "bearer";
/**
* Helper type to represent OAuth2 authorize request body.
*/
export interface OAuth2AuthorizeRequest {
response_type: OAuth2ResponseType;
client_id: string;
client_secret: string;
scope?: string | null;
redirect_uri: string;
state?: string | null;
};
/**
* Helper type to represent OAuth2 authorize response body.
*/
export interface OAuth2CodeResponse {
code: string;
state?: string | null;
};
/**
* Helper type to represent OAuth2 token request body.
*/
export interface OAuth2TokenRequest {
grant_type: OAuth2GrantType;
code?: string | null;
refresh_token?: string | null;
username?: string | null;
password?: string | null;
passkey?: string | null;
client_id: string;
client_secret: string;
redirect_uri?: string | null;
};
/**
* Helper type to represent OAuth2 revoke request body.
*/
export interface OAuth2RevokeRequest {
token: string;
token_type_hint: OAuth2TokenHintType;
};
/**
* Helper type to represent OAuth2 token response body.
*/
export interface OAuth2TokenResponse {
token_type: OAuth2TokenType;
access_token: string;
refresh_token?: string | null;
scope?: string | null;
state?: string | null;
expires_in: number;
};
/**
* Helper type to represent OAuth2 introspection request body.
*/
export interface OAuth2IntrospectionRequest {
token: string;
token_type_hint: OAuth2TokenHintType;
};
/**
* Helper type to represent OAuth2 introspection response body.
*/
export interface OAuth2IntrospectionResponse {
active: boolean;
client_id?: string | null;
username?: string | null;
scope?: string | null;
exp?: number | null;
};

15
src/toml/index.ts Normal file
View File

@ -0,0 +1,15 @@
// Copyright (c) Tribufu. All Rights Reserved.
// SPDX-License-Identifier: MIT
import toml from "toml";
export class TomlSerializer {
/**
* Parse toml string to object.
* @param toml
* @returns {any}
*/
public static fromString(tomlString: string): any {
return toml.parse(tomlString);
}
}

31
src/uuid/index.ts Normal file
View File

@ -0,0 +1,31 @@
// Copyright (c) Tribufu. All Rights Reserved.
// SPDX-License-Identifier: MIT
import { v1 as uuidv1, v4 as uuidv4 } from "uuid";
import { uuidv7 } from "uuidv7";
export class UuidGenerator {
/**
* Generate a version 1 (time-based) UUID.
* @returns {string}
*/
public static v1(): string {
return uuidv1();
}
/**
* Generate a version 4 (random) UUID.
* @returns {string}
*/
public static v4(): string {
return uuidv4();
}
/**
* Generate a version 7 (time-based) UUID.
* @returns {string}
*/
public static v7(): string {
return uuidv7();
}
}