@mereb/shared-packages 0.0.40 → 0.0.42

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/README.md CHANGED
@@ -1,44 +1,63 @@
1
- # Shared Mereb Backend Packages
1
+ # @mereb/shared-packages
2
2
 
3
- This package exposes the shared Fastify/OpenTelemetry helpers used across the backend services. It is built independently with TypeScript and published as `@mereb/shared-packages`.
3
+ Shared backend package consumed by `svc-*` services. It centralizes common auth, config, logging, telemetry, Kafka, Redis, event envelope, and media helper utilities.
4
+
5
+ ## What this package exports
6
+
7
+ Primary root exports from `src/index.ts`:
8
+
9
+ - `auth/jwks`: JWT verification helpers (`verifyJwt`, header parsing helpers)
10
+ - `config/env`: env loading and typed env access (`loadEnv`, `getEnv`, `loadThenGetEnvs`, ...)
11
+ - `logger`: pino logger setup + Fastify logger options
12
+ - `observability/otel`: OpenTelemetry bootstrap helpers
13
+ - `messaging/kafka`: Kafka client/config helpers
14
+ - `cache/redis`: Redis client helpers
15
+ - `events/envelope` and `events/publisher`: integration event envelopes + publish helpers
16
+ - `media/s3`: media URL and upload-key helpers
17
+
18
+ Extra testing exports:
19
+
20
+ - `@mereb/shared-packages/testing/db`
21
+ - `@mereb/shared-packages/testing/kafka`
22
+ - `@mereb/shared-packages/testing/oidc`
4
23
 
5
24
  ## Local development
6
25
 
7
26
  ```bash
8
- pnpm install
9
27
  pnpm --filter @mereb/shared-packages lint
10
28
  pnpm --filter @mereb/shared-packages typecheck
29
+ pnpm --filter @mereb/shared-packages test
11
30
  pnpm --filter @mereb/shared-packages build
12
31
  ```
13
32
 
14
- The compiled JavaScript and type definitions land in `dist/`.
33
+ Build output is written to `dist/`.
15
34
 
16
- ## Jenkins publishing workflow
35
+ ## Versioning and publishing
17
36
 
18
- The folder contains its own `Jenkinsfile` plus `.ci/ci.yml` so it can run through the `mereb-jenkins` shared library just like the services do.
37
+ Version is controlled in `package.json` and published as `@mereb/shared-packages`.
19
38
 
20
- 1. **Version bump** – run `pnpm --filter @mereb/shared-packages version:bump` (optionally pass `major` or `minor`). The script inspects the latest `v*` tag and updates `package.json` to the next version so the release tag and package stay in sync.
21
- 2. **Push to `main`** – Jenkins runs the branch build. After it lints/builds, the `release.autoTag` stage (configured with `allowDirty: true` because the build leaves `node_modules/` around) creates and pushes the next `v<semver>` tag.
22
- 3. **Release stages** – in the same build, the new `releaseStages` block uses that tag (`RELEASE_TAG`) to run `pnpm publish` and then the `release.github` stage publishes release notes.
23
- 4. **Jenkins job** – create a Multibranch Pipeline pointing at this repository. Tags are optional now, but still enable tag discovery if you want Jenkins to react to manual tags.
24
- 5. **Credentials** – add an `npm-registry-token` secret text credential in Jenkins for npm publish, plus `github-credentials` for tagging/GitHub releases. The pipeline writes the npm token to `.npmrc` during the publish stage.
25
- 6. **Registry override (optional)** – set the job environment variable `NPM_REGISTRY` if you publish somewhere other than `https://registry.npmjs.org`.
39
+ Bump version:
26
40
 
27
- Every successful `main` build with a version bump now produces a tag, publishes the package, and creates GitHub release notes without waiting for a second job.
41
+ ```bash
42
+ pnpm --filter @mereb/shared-packages version:bump
43
+ ```
28
44
 
29
- > Tip: The bump script accepts `major`, `minor`, or `patch` (default) so you can control which segment changes: `pnpm --filter @mereb/shared-packages version:bump minor`.
45
+ Optional bump type:
30
46
 
31
- > The repo installs a Husky `pre-commit` hook that automatically runs `pnpm run version:bump` and stages `services/shared/package.json`, keeping the version aligned with the next release tag before every commit. Jenkins sets `HUSKY=0` (see `.ci/ci.yml`) so the hook never runs in CI; locally you can bypass it with `HUSKY=0 git commit ...`.
47
+ ```bash
48
+ pnpm --filter @mereb/shared-packages version:bump minor
49
+ pnpm --filter @mereb/shared-packages version:bump major
50
+ ```
32
51
 
33
- ## Manual publish (fallback)
52
+ The repository includes Jenkins + `.ci/ci.yml` automation for tag/release/publish flow.
34
53
 
35
- If you need to publish outside Jenkins:
54
+ ## Manual publish fallback
36
55
 
37
56
  ```bash
38
57
  cd services/shared
39
58
  pnpm install
40
- pnpm lint && pnpm typecheck && pnpm build
59
+ pnpm lint
60
+ pnpm typecheck
61
+ pnpm build
41
62
  NPM_TOKEN=... pnpm publish --registry https://registry.npmjs.org --no-git-checks
42
63
  ```
43
-
44
- Ensure the git tag matches the package version before publishing so the CI job can take over again on the next release.
@@ -1,5 +1,8 @@
1
1
  import { type JWTPayload } from 'jose';
2
2
  import type { IncomingHttpHeaders } from 'node:http';
3
+ export declare const FULL_ADMIN_ROLES: readonly ["admin", "mereb.admin", "realm-admin"];
4
+ export declare const LIMITED_ADMIN_ROLES: readonly ["moderator", "support", "admin.viewer", "mereb.staff"];
5
+ export declare const READ_ONLY_ADMIN_ROLES: readonly ["admin", "mereb.admin", "realm-admin", "moderator", "support", "admin.viewer", "mereb.staff"];
3
6
  export interface VerifyJwtOptions {
4
7
  issuer: string;
5
8
  audience?: string | string[];
@@ -8,5 +11,9 @@ export interface VerifyJwtOptions {
8
11
  export declare function initJwks(issuer: string): Promise<void>;
9
12
  export declare function verifyJwt(token: string, { issuer, audience }: VerifyJwtOptions): Promise<JWTPayload>;
10
13
  export declare function extractUserId(payload: JWTPayload): string | undefined;
14
+ export declare function extractJwtRoles(payload: JWTPayload): string[];
15
+ export declare function hasAnyRole(roles: readonly string[] | undefined, allowedRoles: readonly string[]): boolean;
16
+ export declare function hasAdminReadAccess(roles: readonly string[] | undefined): boolean;
17
+ export declare function hasFullAdminAccess(roles: readonly string[] | undefined): boolean;
11
18
  export declare function parseAuthHeader(headers: IncomingHttpHeaders): string | undefined;
12
19
  //# sourceMappingURL=jwks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"jwks.d.ts","sourceRoot":"","sources":["../../src/auth/jwks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AACtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAIrD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,QAAQ,CAAC,MAAM,EAAE,MAAM,iBAI5C;AAED,wBAAsB,SAAS,CAC7B,KAAK,EAAE,MAAM,EACb,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,gBAAgB,uBAYvC;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,sBAEhD;AAED,wBAAgB,eAAe,CAC7B,OAAO,EAAE,mBAAmB,GAC3B,MAAM,GAAG,SAAS,CAMpB"}
1
+ {"version":3,"file":"jwks.d.ts","sourceRoot":"","sources":["../../src/auth/jwks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AACtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAIrD,eAAO,MAAM,gBAAgB,kDAAmD,CAAC;AACjF,eAAO,MAAM,mBAAmB,kEAKtB,CAAC;AACX,eAAO,MAAM,qBAAqB,yGAAyD,CAAC;AAE5F,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,QAAQ,CAAC,MAAM,EAAE,MAAM,iBAI5C;AAED,wBAAsB,SAAS,CAC7B,KAAK,EAAE,MAAM,EACb,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,gBAAgB,uBAYvC;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,sBAEhD;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,EAAE,CAS7D;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAKzG;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,GAAG,OAAO,CAEhF;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,GAAG,OAAO,CAEhF;AAED,wBAAgB,eAAe,CAC7B,OAAO,EAAE,mBAAmB,GAC3B,MAAM,GAAG,SAAS,CAMpB"}
package/dist/auth/jwks.js CHANGED
@@ -1,5 +1,13 @@
1
1
  import { createRemoteJWKSet, jwtVerify } from 'jose';
2
2
  let jwks;
3
+ export const FULL_ADMIN_ROLES = ['admin', 'mereb.admin', 'realm-admin'];
4
+ export const LIMITED_ADMIN_ROLES = [
5
+ 'moderator',
6
+ 'support',
7
+ 'admin.viewer',
8
+ 'mereb.staff'
9
+ ];
10
+ export const READ_ONLY_ADMIN_ROLES = [...FULL_ADMIN_ROLES, ...LIMITED_ADMIN_ROLES];
3
11
  export async function initJwks(issuer) {
4
12
  jwks = createRemoteJWKSet(new URL(`${issuer.replace(/\/$/, '')}/protocol/openid-connect/certs`));
5
13
  }
@@ -16,6 +24,25 @@ export async function verifyJwt(token, { issuer, audience }) {
16
24
  export function extractUserId(payload) {
17
25
  return typeof payload.sub === 'string' ? payload.sub : undefined;
18
26
  }
27
+ export function extractJwtRoles(payload) {
28
+ const realmRoles = Array.isArray(payload.realm_access?.roles)
29
+ ? (payload.realm_access?.roles ?? [])
30
+ : [];
31
+ const resourceRoles = Object.values(payload.resource_access ?? {}).flatMap((access) => (Array.isArray(access?.roles) ? access.roles.filter((role) => typeof role === 'string') : []));
32
+ return Array.from(new Set([...realmRoles, ...resourceRoles]));
33
+ }
34
+ export function hasAnyRole(roles, allowedRoles) {
35
+ if (!roles || roles.length === 0) {
36
+ return false;
37
+ }
38
+ return roles.some((role) => allowedRoles.includes(role));
39
+ }
40
+ export function hasAdminReadAccess(roles) {
41
+ return hasAnyRole(roles, READ_ONLY_ADMIN_ROLES);
42
+ }
43
+ export function hasFullAdminAccess(roles) {
44
+ return hasAnyRole(roles, FULL_ADMIN_ROLES);
45
+ }
19
46
  export function parseAuthHeader(headers) {
20
47
  const auth = headers.authorization;
21
48
  if (!auth || !auth.startsWith('Bearer ')) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mereb/shared-packages",
3
- "version": "0.0.40",
3
+ "version": "0.0.42",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",