Commit be32d104 authored by Jukkrapong Ponharn's avatar Jukkrapong Ponharn

Add initial implementation of Policy Request Library with README,...

Add initial implementation of Policy Request Library with README, configuration files, and utility functions
parent 87f00b26
# Dependencies
node_modules/
# Build output
dist/
*.tsbuildinfo
# Logs
*.log
npm-debug.log*
# OS
.DS_Store
Thumbs.db
# IDE
.vscode/
.idea/
*.swp
*.swo
# Testing
coverage/
.nyc_output/
# Environment
.env
.env.local
\ No newline at end of file
# Source files
src/
tsconfig.json
# Tests
*.test.ts
__tests__/
# Development
.vscode/
.idea/
# Git
.git/
.gitignore
# CI/CD
.github/
\ No newline at end of file
# iaam-libs
```markdown
# Policy Request Library
Utility library for creating and encoding policy requests for API headers in React Native applications.
## Installation
### From GitHub
```bash
npm install git+https://git.igridproject.info/iaam/iaam-libs.git
# With specific version/tag
npm install git+https://git.igridproject.info/iaam/iaam-libs.git#v1.0.0
# With yarn
yarn add https://git.igridproject.info/iaam/iaam-libs.git
```
### From package.json
```json
{
"dependencies": {
"iaam-libs": "git+https://git.igridproject.info/iaam/iaam-libs.git"
}
}
```
\ No newline at end of file
{
"name": "iaam-libs",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "iaam-libs",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"axios": "^1.13.2",
"base-64": "^1.0.0"
},
"devDependencies": {
"@types/base-64": "^1.0.0",
"@types/node": "^20.0.0",
"typescript": "^5.0.0"
},
"peerDependencies": {
"react-native": ">=0.60.0"
},
"peerDependenciesMeta": {
"react-native": {
"optional": true
}
}
},
"node_modules/@types/base-64": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@types/base-64/-/base-64-1.0.2.tgz",
"integrity": "sha512-uPgKMmM9fmn7I+Zi6YBqctOye4SlJsHKcisjHIMWpb2YKZRc36GpKyNuQ03JcT+oNXg1m7Uv4wU94EVltn8/cw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "20.19.25",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz",
"integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/base-64": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz",
"integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==",
"license": "MIT"
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/follow-redirects": {
"version": "1.15.11",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
}
}
}
{
"name": "iaam-libs",
"version": "1.0.0",
"description": "Policy request utility for encoding and sending via API headers",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"prepare": "npm run build",
"test": "jest"
},
"keywords": [
"policy",
"request",
"base64",
"headers",
"react-native"
],
"author": "DSS@NECTEC",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://git.igridproject.info/iaam/iaam-libs.git"
},
"files": [
"dist",
"README.md",
"LICENSE"
],
"dependencies": {
"axios": "^1.13.2",
"base-64": "^1.0.0"
},
"devDependencies": {
"@types/base-64": "^1.0.0",
"@types/node": "^20.0.0",
"typescript": "^5.0.0"
},
"peerDependencies": {
"react-native": ">=0.60.0"
},
"peerDependenciesMeta": {
"react-native": {
"optional": true
}
}
}
import { encode as base64Encode, decode as base64Decode } from 'base-64';
import axios from 'axios';
/**
* Resource Parameter Structure
*/
export interface ResourceParam {
path?: string;
[key: string]: any;
}
/**
* Resource Structure
*/
export interface Resource {
type?: string;
name?: string;
param?: ResourceParam;
[key: string]: any;
}
/**
* Subject Structure
*/
export interface Subject {
[key: string]: any;
}
/**
* Environment Structure
*/
export interface Environment {
[key: string]: any;
}
/**
* Policy Request Configuration
*/
export interface PolicyConfig {
subject?: Subject;
resource?: Resource;
action?: string;
environment?: Environment;
}
/**
* Policy Request Data Structure
*/
export interface PolicyRequestData {
subject: Subject;
resource: Resource;
action: string;
environment: Environment;
}
async function getPublicIP(): Promise<string> {
try {
const response = await axios.get("https://api.ipify.org/?format=json");
const ip = response.data?.ip;
if (!ip) {
throw new Error("IP address not found in response");
}
return ip;
} catch (error) {
// Do not throw here to avoid breaking callers that expect a string.
// Log the error and return empty string as a safe fallback.
console.error('getPublicIP error:', error);
return '';
}
}
/**
* Policy Request Utility Functions
*/
export class PolicyRequest {
/**
* Create policy request and encode to Base64
* @param config - Policy configuration object
* @returns Base64 encoded string
*/
static create(config: PolicyConfig = {}): string {
const data: PolicyRequestData = {
subject: config.subject || {},
resource: {
type: config.resource?.type || 'API',
name: config.resource?.name || '',
param: {
path: config.resource?.param?.path || '',
...config.resource?.param,
},
...config.resource,
},
action: config.action || '',
environment: config.environment || {},
};
const jsonString = JSON.stringify(data);
return base64Encode(jsonString);
}
/**
* Create policy request headers
* @param config - Policy configuration object
* @param headerKey - Custom header key name (default: 'X-Policy-Request')
* @returns Headers object
*/
static async createHeaders(
clientName: string,
url: string,
method: string,
subject: Subject = {},
ipVerify: Boolean = false,
headerKey: string = 'X-Policy-Request'
): Promise<Record<string, string>> {
let environment: any = {
"resource": {
"type": "WEB",
"name": clientName,
"timestamp": new Date(),
}
}
if (ipVerify) {
environment["public_ip"] = await getPublicIP();
}
const policyConfig = {
subject: subject,
resource: {
"type": "API",
"name": "",
"param": {
"path": new URL(url).pathname
}
},
action: method,
environment: environment
}
const encodedPolicy = PolicyRequest.create(policyConfig);
return {
[headerKey]: encodedPolicy,
};
}
/**
* Decode Base64 policy request back to object
* @param base64String - Base64 encoded policy request
* @returns Decoded policy data
*/
static decode(base64String: string): PolicyRequestData {
try {
const decoded = base64Decode(base64String);
return JSON.parse(decoded);
} catch (error) {
throw new Error(
`Invalid policy request format: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
/**
* Validate policy request format
* @param data - Policy data to validate
* @returns true if valid, false otherwise
*/
static validate(data: any): boolean {
if (!data || typeof data !== 'object') {
return false;
}
// Check required fields exist
if (!('subject' in data) || !('resource' in data) || !('action' in data) || !('environment' in data)) {
return false;
}
// Check subject and environment are objects
if (typeof data.subject !== 'object' || typeof data.environment !== 'object') {
return false;
}
// Check resource structure
if (typeof data.resource !== 'object') {
return false;
}
// Check action is string
if (typeof data.action !== 'string') {
return false;
}
return true;
}
/**
* Create policy request with validation
* @param config - Policy configuration object
* @returns Base64 encoded string
* @throws Error if validation fails
*/
static createSafe(config: PolicyConfig = {}): string {
const data: PolicyRequestData = {
subject: config.subject || {},
resource: {
type: config.resource?.type || '',
name: config.resource?.name || '',
param: {
path: config.resource?.param?.path || '',
...config.resource?.param,
},
...config.resource,
},
action: config.action || '',
environment: config.environment || {},
};
if (!PolicyRequest.validate(data)) {
throw new Error('Invalid policy request data');
}
const jsonString = JSON.stringify(data);
return base64Encode(jsonString);
}
}
// Default export
export default PolicyRequest;
\ No newline at end of file
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment