opticedge-cloud-utils 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.js ADDED
@@ -0,0 +1,29 @@
1
+ module.exports = {
2
+ root: true,
3
+ parser: '@typescript-eslint/parser',
4
+ parserOptions: {
5
+ ecmaVersion: 2020, // modern JS features
6
+ sourceType: 'module',
7
+ },
8
+ env: {
9
+ node: true,
10
+ jest: true,
11
+ es2020: true,
12
+ },
13
+ plugins: ['@typescript-eslint', 'jest'],
14
+ extends: [
15
+ 'eslint:recommended',
16
+ 'plugin:@typescript-eslint/recommended',
17
+ 'plugin:jest/recommended',
18
+ ],
19
+ rules: {
20
+ // Your custom rules here, for example:
21
+ 'no-console': 'warn',
22
+ '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
23
+ },
24
+ settings: {
25
+ jest: {
26
+ version: 29, // or your jest version
27
+ },
28
+ },
29
+ };
@@ -0,0 +1,7 @@
1
+ import { Request } from '@google-cloud/functions-framework';
2
+ interface VerifyOptions {
3
+ allowedAudience: string;
4
+ allowedServiceAccount: string;
5
+ }
6
+ export declare function verifyRequest(req: Request, options: VerifyOptions): Promise<boolean>;
7
+ export {};
package/dist/verify.js ADDED
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.verifyRequest = verifyRequest;
4
+ const google_auth_library_1 = require("google-auth-library");
5
+ async function verifyRequest(req, options) {
6
+ const authHeader = req.headers['authorization'];
7
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
8
+ return false;
9
+ }
10
+ const token = authHeader.split(' ')[1];
11
+ const client = new google_auth_library_1.OAuth2Client();
12
+ try {
13
+ const ticket = await client.verifyIdToken({
14
+ idToken: token,
15
+ audience: options.allowedAudience,
16
+ });
17
+ const payload = ticket.getPayload();
18
+ return payload?.email === options.allowedServiceAccount;
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const verify_1 = require("./verify");
4
+ // Mock the google-auth-library
5
+ jest.mock('google-auth-library', () => {
6
+ return {
7
+ OAuth2Client: jest.fn().mockImplementation(() => ({
8
+ verifyIdToken: jest.fn(({ idToken, audience }) => {
9
+ if (idToken === 'valid-token' && audience === 'audience') {
10
+ return {
11
+ getPayload: () => ({
12
+ email: 'allowed@example.com',
13
+ }),
14
+ };
15
+ }
16
+ else if (idToken === 'wrong-email-token' && audience === 'audience') {
17
+ return {
18
+ getPayload: () => ({
19
+ email: 'unauthorized@example.com',
20
+ }),
21
+ };
22
+ }
23
+ throw new Error('Invalid token');
24
+ }),
25
+ })),
26
+ };
27
+ });
28
+ describe('verifyRequest', () => {
29
+ it('returns true for valid token and matching email', async () => {
30
+ const mockReq = {
31
+ headers: {
32
+ authorization: 'Bearer valid-token',
33
+ },
34
+ };
35
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
+ const result = await (0, verify_1.verifyRequest)(mockReq, {
37
+ allowedAudience: 'audience',
38
+ allowedServiceAccount: 'allowed@example.com',
39
+ });
40
+ expect(result).toBe(true);
41
+ });
42
+ it('returns false for invalid token', async () => {
43
+ const mockReq = {
44
+ headers: {
45
+ authorization: 'Bearer invalid-token',
46
+ },
47
+ };
48
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
+ const result = await (0, verify_1.verifyRequest)(mockReq, {
50
+ allowedAudience: 'audience',
51
+ allowedServiceAccount: 'allowed@example.com',
52
+ });
53
+ expect(result).toBe(false);
54
+ });
55
+ it('returns false for missing authorization header', async () => {
56
+ const mockReq = {
57
+ headers: {},
58
+ };
59
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
+ const result = await (0, verify_1.verifyRequest)(mockReq, {
61
+ allowedAudience: 'audience',
62
+ allowedServiceAccount: 'allowed@example.com',
63
+ });
64
+ expect(result).toBe(false);
65
+ });
66
+ it('returns false for incorrect email', async () => {
67
+ const mockReq = {
68
+ headers: {
69
+ authorization: 'Bearer wrong-email-token',
70
+ },
71
+ };
72
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
73
+ const result = await (0, verify_1.verifyRequest)(mockReq, {
74
+ allowedAudience: 'audience',
75
+ allowedServiceAccount: 'allowed@example.com',
76
+ });
77
+ expect(result).toBe(false);
78
+ });
79
+ });
package/jest.config.js ADDED
@@ -0,0 +1,11 @@
1
+ const { createDefaultPreset } = require("ts-jest");
2
+
3
+ const tsJestTransformCfg = createDefaultPreset().transform;
4
+
5
+ /** @type {import("jest").Config} **/
6
+ module.exports = {
7
+ testEnvironment: "node",
8
+ transform: {
9
+ ...tsJestTransformCfg,
10
+ },
11
+ };
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "opticedge-cloud-utils",
3
+ "version": "1.0.0",
4
+ "description": "Common utilities for cloud functions",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "test": "jest --coverage",
10
+ "lint": "eslint . --ext .ts",
11
+ "prepare": "npm run build"
12
+ },
13
+ "author": "Evans Musonda",
14
+ "license": "MIT",
15
+ "dependencies": {
16
+ "google-auth-library": "^9.15.1"
17
+ },
18
+ "devDependencies": {
19
+ "@google-cloud/functions-framework": "^4.0.0",
20
+ "@types/jest": "^29.5.14",
21
+ "@types/node": "^22.15.23",
22
+ "@typescript-eslint/eslint-plugin": "^8.33.0",
23
+ "@typescript-eslint/parser": "^8.33.0",
24
+ "eslint": "^8.57.1",
25
+ "eslint-plugin-jest": "^28.11.1",
26
+ "jest": "^29.7.0",
27
+ "ts-jest": "^29.3.4",
28
+ "typescript": "^5.8.3"
29
+ }
30
+ }
@@ -0,0 +1,89 @@
1
+ import { verifyRequest } from './verify';
2
+
3
+ // Mock the google-auth-library
4
+ jest.mock('google-auth-library', () => {
5
+ return {
6
+ OAuth2Client: jest.fn().mockImplementation(() => ({
7
+ verifyIdToken: jest.fn(({ idToken, audience }) => {
8
+ if (idToken === 'valid-token' && audience === 'audience') {
9
+ return {
10
+ getPayload: () => ({
11
+ email: 'allowed@example.com',
12
+ }),
13
+ };
14
+ } else if (idToken === 'wrong-email-token' && audience === 'audience') {
15
+ return {
16
+ getPayload: () => ({
17
+ email: 'unauthorized@example.com',
18
+ }),
19
+ };
20
+ }
21
+ throw new Error('Invalid token');
22
+ }),
23
+ })),
24
+ };
25
+ });
26
+
27
+ describe('verifyRequest', () => {
28
+ it('returns true for valid token and matching email', async () => {
29
+ const mockReq = {
30
+ headers: {
31
+ authorization: 'Bearer valid-token',
32
+ },
33
+ };
34
+
35
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
+ const result = await verifyRequest(mockReq as any, {
37
+ allowedAudience: 'audience',
38
+ allowedServiceAccount: 'allowed@example.com',
39
+ });
40
+
41
+ expect(result).toBe(true);
42
+ });
43
+
44
+ it('returns false for invalid token', async () => {
45
+ const mockReq = {
46
+ headers: {
47
+ authorization: 'Bearer invalid-token',
48
+ },
49
+ };
50
+
51
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
52
+ const result = await verifyRequest(mockReq as any, {
53
+ allowedAudience: 'audience',
54
+ allowedServiceAccount: 'allowed@example.com',
55
+ });
56
+
57
+ expect(result).toBe(false);
58
+ });
59
+
60
+ it('returns false for missing authorization header', async () => {
61
+ const mockReq = {
62
+ headers: {},
63
+ };
64
+
65
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
+ const result = await verifyRequest(mockReq as any, {
67
+ allowedAudience: 'audience',
68
+ allowedServiceAccount: 'allowed@example.com',
69
+ });
70
+
71
+ expect(result).toBe(false);
72
+ });
73
+
74
+ it('returns false for incorrect email', async () => {
75
+ const mockReq = {
76
+ headers: {
77
+ authorization: 'Bearer wrong-email-token',
78
+ },
79
+ };
80
+
81
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
82
+ const result = await verifyRequest(mockReq as any, {
83
+ allowedAudience: 'audience',
84
+ allowedServiceAccount: 'allowed@example.com',
85
+ });
86
+
87
+ expect(result).toBe(false);
88
+ });
89
+ });
@@ -0,0 +1,32 @@
1
+ import { OAuth2Client } from 'google-auth-library';
2
+ import { Request } from '@google-cloud/functions-framework';
3
+
4
+ interface VerifyOptions {
5
+ allowedAudience: string;
6
+ allowedServiceAccount: string;
7
+ }
8
+
9
+ export async function verifyRequest(
10
+ req: Request,
11
+ options: VerifyOptions
12
+ ): Promise<boolean> {
13
+ const authHeader = req.headers['authorization'];
14
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
15
+ return false;
16
+ }
17
+
18
+ const token = authHeader.split(' ')[1];
19
+ const client = new OAuth2Client();
20
+
21
+ try {
22
+ const ticket = await client.verifyIdToken({
23
+ idToken: token,
24
+ audience: options.allowedAudience,
25
+ });
26
+
27
+ const payload = ticket.getPayload();
28
+ return payload?.email === options.allowedServiceAccount;
29
+ } catch {
30
+ return false;
31
+ }
32
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "CommonJS",
5
+ "declaration": true,
6
+ "outDir": "./dist",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "moduleResolution": "node",
10
+ "skipLibCheck": true,
11
+ "resolveJsonModule": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "types": ["node", "jest"]
14
+ },
15
+ "include": ["src"],
16
+ "exclude": ["node_modules", "dist"]
17
+ }