@samanbayaka/core-gtwy 0.0.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,58 @@
1
+ # demo
2
+
3
+ ## 0.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - chore: pkg sanitize
8
+ - Updated dependencies
9
+ - @samanbayaka/core@0.0.11
10
+
11
+ ## 0.0.2
12
+
13
+ ### Patch Changes
14
+
15
+ - chore: update workspace lockfile
16
+ - Updated dependencies
17
+ - @samanbayaka/core@0.0.11
18
+
19
+ ## 0.0.3
20
+
21
+ ### Patch Changes
22
+
23
+ - chore: update workspac
24
+ - Updated dependencies
25
+ - @samanbayaka/core@0.0.11
26
+
27
+ ## 0.0.2
28
+
29
+ ### Patch Changes
30
+
31
+ - 314257a: new
32
+ - Updated dependencies [314257a]
33
+ - @samanbayaka/core@0.0.11
34
+
35
+ ## 0.0.2
36
+
37
+ ### Patch Changes
38
+
39
+ - fixed
40
+ - Updated dependencies
41
+ - @samanbayaka/core@0.0.11
42
+
43
+ ## 0.1.0
44
+
45
+ ### Minor Changes
46
+
47
+ - dfc8940: fixed
48
+
49
+ ### Patch Changes
50
+
51
+ - fixed
52
+ - 65fb2ca: fixed
53
+ - b845d66: fixed
54
+ - Updated dependencies
55
+ - Updated dependencies [65fb2ca]
56
+ - Updated dependencies [b845d66]
57
+ - Updated dependencies [dfc8940]
58
+ - @samanbayaka/core@0.1.0
package/index.mjs ADDED
@@ -0,0 +1,207 @@
1
+ import ApiGateway from "moleculer-web"
2
+ import cookieParser from "cookie-parser"
3
+ import bodyParser from "body-parser"
4
+ import helmet from "helmet"
5
+ import compression from "compression"
6
+
7
+ import core from "@samanbayaka/core"
8
+ import * as tokenValidator from './src/access-token-validator.mjs'
9
+
10
+ /**
11
+ * Regx pattern to allow cors sutes and cors origins
12
+ * @type {String}
13
+ */
14
+ const patternTxt = (await core.getConfigs(`/config/sbk/edge/CORS`)) || undefined
15
+ const reqOriginRegex = patternTxt ? new RegExp(patternTxt, "i") : undefined
16
+
17
+ const gtwySchema = {
18
+ name: "gtwy",
19
+ mixins: [
20
+ ApiGateway,
21
+ ],
22
+
23
+ settings: {
24
+ port: 3000,
25
+
26
+ /**
27
+ * Middleware mode (for ExpressJS)
28
+ */
29
+ middleware: false,
30
+
31
+ /**
32
+ * Exposed global path prefix
33
+ */
34
+ // path: "/sbk",
35
+
36
+ /**
37
+ * Global middlewares. Applied to all routes.
38
+ */
39
+ use: [
40
+ compression(),
41
+ cookieParser(),
42
+ bodyParser.raw({
43
+ type: [
44
+ "application/octet-stream",
45
+ "multipart/form-data",
46
+ ],
47
+ limit: "50mb"
48
+ }),
49
+ helmet(),
50
+ ],
51
+
52
+ cors: reqOriginRegex ? {
53
+ origin: (reqOrigin) => {
54
+ if (!reqOrigin) return true
55
+ const { hostname } = new URL(reqOrigin)
56
+ return reqOriginRegex.test(hostname)
57
+ },
58
+
59
+ credentials: true
60
+ } : undefined,
61
+
62
+ routes: [
63
+ // // Public routes
64
+ // {
65
+ // path: "",
66
+ // authorization: false,
67
+
68
+ // onBeforeCall(ctx, route, req, res) {
69
+ // ctx.meta.method = req?.method
70
+ // ctx.meta.headers = req?.headers
71
+ // ctx.meta.cookies = req?.cookies
72
+ // },
73
+
74
+ // whitelist: [
75
+ // "gateway.apiDesc",
76
+ // "gateway.apiVer",
77
+ // /**
78
+ // * Access any actions in 'system' service
79
+ // */
80
+ // /^auth\.(?!listAliases$)\w+(?:\.\w+)*$/
81
+ // ],
82
+
83
+
84
+ // aliases: {
85
+ // "GET /sbk/version": "gateway.apiVer",
86
+ // "GET /sbk/description": "gateway.apiDesc",
87
+ // },
88
+
89
+ // autoAliases: true,
90
+ // mergeParams: false,
91
+ // logging: false,
92
+
93
+ // },
94
+
95
+ /**
96
+ * Protected API routes
97
+ */
98
+ {
99
+ path: "/sbk/api",
100
+ authentication: true,
101
+ authorization: true,
102
+ onBeforeCall(ctx, route, req, res) {
103
+ ctx.meta.method = req?.method
104
+ ctx.meta.headers = req?.headers
105
+ ctx.meta.cookies = req?.cookies
106
+ },
107
+ whitelist: [
108
+ /**
109
+ * Access any actions except 'gateway', 'system', 'oidc', and 'auth' service
110
+ */
111
+ /^(?!(gateway|system|core.auth|auth|oidc|asset|dbs.pg)\.)\w+(?:\.\w+)*$/
112
+ ],
113
+
114
+ autoAliases: true,
115
+ mergeParams: false,
116
+ mappingPolicy: "restrict",
117
+ logging: true,
118
+
119
+ /**
120
+ * Use bodyparser module
121
+ */
122
+ bodyParsers: {
123
+ json: true,
124
+ urlencoded: { extended: true },
125
+ text: {
126
+ type: [
127
+ "text/plain",
128
+ "application/xml",
129
+ "application/yaml",
130
+ "text/xml",
131
+ ]
132
+ }
133
+ },
134
+ },
135
+ ],
136
+
137
+
138
+ // /**
139
+ // * Logging request parameters with 'info' level
140
+ // */
141
+ logRequestParams: null,
142
+
143
+
144
+ // /**
145
+ // * Logging response data with 'debug' level
146
+ // */
147
+ logResponseData: false,
148
+
149
+
150
+ /**
151
+ * Global error handler
152
+ */
153
+ onError(req, res, err) {
154
+
155
+ core.httpErrorFormatter(req, res, err)
156
+
157
+ /**
158
+ * Custom logging
159
+ */
160
+ this.logResponse(req, res)
161
+ }
162
+
163
+ },
164
+
165
+
166
+ // actions: {
167
+ // apiDesc:{
168
+ // openapi: {
169
+ // summary: "API Description",
170
+ // description: "Returns a textual description of the API."
171
+ // },
172
+ // handler: (ctx) => {
173
+ // return ctx.call("system.info.apiDesc")
174
+ // }
175
+ // },
176
+
177
+ // apiVer: {
178
+ // openapi: {
179
+ // summary: "API Version",
180
+ // description: "Returns the current version of the API."
181
+ // },
182
+ // handler: (ctx) => {
183
+ // return ctx.call("system.info.apiVer")
184
+ // },
185
+ // },
186
+ // },
187
+
188
+
189
+ methods: {
190
+ /**
191
+ * This overrides the errorHandler method found in the official file
192
+ */
193
+ errorHandler(req, res, err) {
194
+ // // don't log client side errors unless it's configured
195
+ // if (this.settings.log4XXResponses || (err && !_.inRange(err.code, 400, 500))) {
196
+ // this.logger.error(" Request error!", err.name, ":", err.message, "\n", err.stack, "\nData:", err.data);
197
+ // }
198
+ this.sendError(req, res, err)
199
+ },
200
+ authenticate: tokenValidator.authenticate,
201
+ authorize: tokenValidator.authorize,
202
+ },
203
+ }
204
+ export default {
205
+ ...core,
206
+ schema: gtwySchema,
207
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@samanbayaka/core-gtwy",
3
+ "version": "0.0.1",
4
+ "description": "",
5
+ "main": "index.mjs",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": "./index.mjs"
9
+ },
10
+ "files": [
11
+ "index.mjs",
12
+ "src/",
13
+ "README.md",
14
+ "CHANGELOG.md"
15
+ ],
16
+ "keywords": [],
17
+ "author": "",
18
+ "license": "ISC",
19
+ "dependencies": {
20
+ "body-parser": "^2.3.0",
21
+ "compression": "^1.8.1",
22
+ "cookie-parser": "^1.4.7",
23
+ "helmet": "^8.2.0",
24
+ "jsonwebtoken": "^9.0.3",
25
+ "jwks-rsa": "^4.0.1",
26
+ "moleculer-web": "^0.11.0",
27
+ "@samanbayaka/core": "0.0.10"
28
+ },
29
+ "scripts": {
30
+ "test": "echo \"Error: no test specified\" && exit 1"
31
+ }
32
+ }
@@ -0,0 +1,249 @@
1
+ import { Errors } from "moleculer"
2
+ import ApiGateway from "moleculer-web"
3
+ import jwt from "jsonwebtoken"
4
+ import jwksClient from "jwks-rsa"
5
+
6
+ import core from "@samanbayaka/core"
7
+
8
+ const ACCESS_ROLES = await core.getConfigs('/config/sbk/edge/yaml/roles')
9
+ const OPENID_CONFIG = await core.getConfigs('/config/sbk/edge/yaml/auth')
10
+ export const {clientId} = OPENID_CONFIG
11
+
12
+ /**
13
+ * Initializes OpenID Connect configuration and JWKS client.
14
+ *
15
+ * Fetches the OpenID configuration document from the configured
16
+ * issuer endpoint and creates a cached JWKS client for JWT
17
+ * signature verification.
18
+ *
19
+ * The JWKS client supports:
20
+ * - Automatic key retrieval
21
+ * - In-memory caching
22
+ * - Request rate limiting
23
+ *
24
+ * @async
25
+ * @function getOpenidConfigs
26
+ *
27
+ * @throws {Error} Throws if the OpenID configuration request fails
28
+ * or the response cannot be parsed.
29
+ *
30
+ * @returns {Promise<void>}
31
+ */
32
+ const getOpenidConfigs = async () => {
33
+
34
+ const response = await fetch(OPENID_CONFIG.url)
35
+
36
+ if ( !response.ok ) {
37
+ throw new Error(
38
+ `OpenID configuration endpoint failed with status "${response.status}"`
39
+ )
40
+ }
41
+
42
+ return await response.json()
43
+ }
44
+
45
+ export const {
46
+ issuer,
47
+ jwks_uri: jwksUri,
48
+ authorization_endpoint: authorizationEndpoint,
49
+ token_endpoint: tokenEndpoint,
50
+ end_session_endpoint: endSessionEndpoint,
51
+ } = await getOpenidConfigs()
52
+
53
+ const client = jwksClient({
54
+ ...OPENID_CONFIG.jwksClient,
55
+ jwksUri: jwksUri,
56
+ })
57
+
58
+ const issuers = ((iss) => {
59
+ const httpUrl = new URL(iss)
60
+ httpUrl.protocol = "http:"
61
+ httpUrl.port = String(OPENID_CONFIG.kcPort)
62
+ return [
63
+ iss,
64
+ httpUrl.toString(),
65
+ ]
66
+ })(issuer)
67
+
68
+
69
+ /**
70
+ * Retrieves the public signing key corresponding to a JWT `kid`.
71
+ *
72
+ * Used by JWT verification libraries (such as `jsonwebtoken`)
73
+ * to dynamically resolve signing keys from a JWKS endpoint.
74
+ *
75
+ * @param {Object} header - Decoded JWT header.
76
+ * @param {string} header.kid - Key ID used to identify the signing key.
77
+ * @param {Function} callback - Callback function invoked after key retrieval.
78
+ * @param {Error|null} callback.err - Error object if key retrieval fails.
79
+ * @param {string|null} callback.key - PEM formatted public key.
80
+ *
81
+ * @returns {void}
82
+ */
83
+ const getKey = (header, callback) => {
84
+
85
+ client.getSigningKey(
86
+ header.kid,
87
+ (err, key) => {
88
+
89
+ if (err) {
90
+ callback(err)
91
+ return
92
+ }
93
+
94
+ callback(
95
+ null,
96
+ key.publicKey || key.rsaPublicKey
97
+ )
98
+ }
99
+ )
100
+
101
+ }
102
+
103
+
104
+
105
+ /**
106
+ * Authenticates the requester by verifying the access token.
107
+ * The token can be provided either in the Bearer Authorization header
108
+ * or in cookies.
109
+ *
110
+ * @param {Context} ctx - Moleculer service context
111
+ * @param {Object} route - REST route configuration object
112
+ * @param {HTTP request<Object>} req - HTTP request object
113
+ * @returns {Promise<Object>} Authenticated user / authorization result
114
+ */
115
+ export const authenticate = async (ctx, route, req, res) => {
116
+
117
+ let token = null
118
+
119
+ const auth =
120
+ req.headers.authorization
121
+
122
+ if (auth?.startsWith("Bearer ")) {
123
+ token = auth.replace(/^Bearer\s+/i, "")
124
+ }
125
+
126
+
127
+ if (
128
+ !token &&
129
+ req.cookies?.WbsedclAccessToken
130
+ ) {
131
+ token = req.cookies?.WbsedclAccessToken
132
+ }
133
+
134
+
135
+ if (!token) {
136
+ throw new ApiGateway.Errors.UnAuthorizedError(
137
+ ApiGateway.Errors.ERR_NO_TOKEN
138
+ )
139
+ }
140
+
141
+ try {
142
+ const decoded =
143
+ await new Promise((resolve, reject) => {
144
+
145
+ jwt.verify(
146
+ token,
147
+ getKey,
148
+ {
149
+ algorithms: OPENID_CONFIG.jwtVerifyAlgo,
150
+ issuers,
151
+ // audience: OPENID_CONFIG.audience,
152
+ },
153
+
154
+ (err, dec) => {
155
+
156
+ if (err) {
157
+ reject(err)
158
+ return
159
+ }
160
+
161
+ resolve(dec)
162
+ }
163
+ )
164
+ })
165
+
166
+ ctx.broker.logger.debug({tag: "SBK", message: "Decoded Token Object", token: decoded, OPENID_CONFIG, issuers})
167
+ // ctx.meta.user = {
168
+ // sessionId: decoded?.sid,
169
+ // id: decoded?.sub,
170
+ // userId: (decoded?.sub).split(":").at(-1),
171
+ // username: decoded?.preferred_username,
172
+ // firstName: decoded?.given_name,
173
+ // lastName: decoded?.family_name,
174
+ // fullName: [...new Set(decoded?.name.split(/\s+/))].join(" "),
175
+ // emailIds: decoded?.email,
176
+ // mobileNos: decoded?.mobile_no,
177
+ // userRoles: decoded?.user_role,
178
+ // }
179
+ return decoded
180
+
181
+ } catch (err) {
182
+ ctx.broker.logger.debug({tag: "SBK", OPENID_CONFIG})
183
+ throw new ApiGateway.Errors.UnAuthorizedError(
184
+ ApiGateway.Errors.ERR_INVALID_TOKEN
185
+ )
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Verifies the user's roles and access grants before authorizing
191
+ * access to the requested resource.
192
+ * @param {object} ctx [description]
193
+ * @param {object} route [description]
194
+ * @param {object} req [description]
195
+ * @param {object} res [description]
196
+ * @return {object} [description]
197
+ */
198
+ export const authorize = async(ctx, route, req, res) => {
199
+
200
+ ctx.broker.logger.debug({tag: "SBK", message: "User Profile"}, ctx.meta.user, route?.path)
201
+
202
+ if( ctx.meta?.user?.preferred_username === undefined ){
203
+ throw new ApiGateway.Errors.UnAuthorizedError(
204
+ ApiGateway.Errors.ERR_INVALID_TOKEN
205
+ )
206
+ }
207
+
208
+ ctx.broker.logger.debug({tag: "SBK", message: "ACCESS_ROLES", ACCESS_ROLES, resource_access: ctx.meta.user.resource_access})
209
+
210
+ // resource_access
211
+
212
+ // const aa = new RegExp("^/system/oidc(?:/.*)?$", "i")
213
+ // if()
214
+
215
+ // if( req.url.startsWith("/system" ){
216
+
217
+ // }
218
+
219
+
220
+ // /**
221
+ // * Resticed accessor can't access all resources
222
+ // */
223
+ // if( ctx.meta?.user?.preferred_username == OPENID_CONFIG.rAccessor ){
224
+ // throw new Errors.MoleculerError(
225
+ // "You are not authorized to access this resource",
226
+ // 403,
227
+ // "FORBIDDEN"
228
+ // )
229
+ // }
230
+
231
+ // if( ctx.meta.user?.azp == clientId && req.url.startsWith(OPENID_CONFIG.rRoute)) {
232
+ // throw new Errors.MoleculerError(
233
+ // "You are not authorized to access this resource",
234
+ // 403,
235
+ // "FORBIDDEN"
236
+ // )
237
+ // }
238
+
239
+ // if( ctx.meta.user?.azp == OPENID_CONFIG.rAccessor && !req.url.startsWith(OPENID_CONFIG.rRoute)) {
240
+ // throw new Errors.MoleculerError(
241
+ // "You are not authorized to access this resource",
242
+ // 403,
243
+ // "FORBIDDEN"
244
+ // )
245
+ // }
246
+
247
+ return Promise.resolve(ctx)
248
+ }
249
+