From bcad9c6390faf768e26f88441eabfe2fdac698d8 Mon Sep 17 00:00:00 2001 From: Alberto Ricart Date: Tue, 9 Apr 2024 14:38:43 -0500 Subject: [PATCH] [INTERNAL] mechanism to retrieve connection info [INTERNAL] mechanism to retrieve request info --- README.md | 2 +- nats-base-client/core.ts | 61 +++++++++++++++++++++++++++++++++ nats-base-client/msg.ts | 17 +++++++++ nats-base-client/nats.ts | 11 ++++++ tests/auth_test.ts | 74 +++++++++++++++++++++++++++++++++++----- 5 files changed, 156 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 11e9b9b8..d35ce066 100644 --- a/README.md +++ b/README.md @@ -811,7 +811,7 @@ The following is the list of connection options and default values. ### TlsOptions | Option | Default | Description | -| ---------------- | ------- |---------------------------------------------------------------------------------------------------------------------------------| +| ---------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------- | | `ca` | N/A | CA certificate | | `caFile` | | CA certificate filepath | | `cert` | N/A | Client certificate | diff --git a/nats-base-client/core.ts b/nats-base-client/core.ts index b3669264..e2d649f3 100644 --- a/nats-base-client/core.ts +++ b/nats-base-client/core.ts @@ -1434,3 +1434,64 @@ export enum ServiceVerb { STATS = "STATS", INFO = "INFO", } + +export type Context = { + server: ContextServer; + data: ContextUser; +}; + +export type ContextServer = { + name: string; + host: string; + id: string; + version: string; + tags: string[]; + jetstream: boolean; + flags: number; + seq: number; + time: Date; +}; + +export type ContextUser = { + user: string; + account: string; + permissions?: { + publish?: ContextPermission; + subscribe?: ContextPermission; + responses?: ContextResponsePermission; + }; +}; + +export type ContextPermission = { + deny?: string[]; + allow?: string[]; +}; + +export type ContextResponsePermission = { + max: number; + ttl: number; +}; + +export type RequestInfo = { + acc: string; + rtt: number; + start?: Date; + host?: string; + id?: string; + svc?: string; + user?: string; + name?: string; + lang?: string; + ver?: string; + server?: string; + cluster?: string; + alts?: string[]; + stop?: Date; + jwt?: string; + issuer_key?: string; + name_tag?: string; + tags?: string[]; + client_type?: string; + client_id?: string; + nonce?: string; +}; diff --git a/nats-base-client/msg.ts b/nats-base-client/msg.ts index d172fdc7..8193554e 100644 --- a/nats-base-client/msg.ts +++ b/nats-base-client/msg.ts @@ -22,6 +22,7 @@ import { MsgHdrs, NatsError, Publisher, + RequestInfo, ReviverFn, } from "./core.ts"; @@ -113,4 +114,20 @@ export class MsgImpl implements Msg { string(): string { return TD.decode(this.data); } + + requestInfo(): RequestInfo | null { + const v = this.headers?.get("Nats-Request-Info"); + if (v) { + return JSON.parse( + v, + function (this: unknown, key: string, value: unknown): unknown { + if ((key === "start" || key === "stop") && value !== "") { + return new Date(Date.parse(value as string)); + } + return value; + }, + ) as RequestInfo; + } + return null; + } } diff --git a/nats-base-client/nats.ts b/nats-base-client/nats.ts index 9a68c1ad..3f9c7b64 100644 --- a/nats-base-client/nats.ts +++ b/nats-base-client/nats.ts @@ -36,6 +36,7 @@ import { ServiceClientImpl } from "./serviceclient.ts"; import { JetStreamClient, JetStreamManager } from "../jetstream/types.ts"; import { ConnectionOptions, + Context, createInbox, ErrorCode, JetStreamManagerOptions, @@ -477,6 +478,16 @@ export class NatsConnectionImpl implements NatsConnection { return this.protocol.isClosed() ? undefined : this.protocol.info; } + async context(): Promise { + const r = await this.request(`$SYS.REQ.USER.INFO`); + return r.json((key, value) => { + if (key === "time") { + return new Date(Date.parse(value)); + } + return value; + }); + } + stats(): Stats { return { inBytes: this.protocol.inBytes, diff --git a/tests/auth_test.ts b/tests/auth_test.ts index cb05c3ed..3b635d10 100644 --- a/tests/auth_test.ts +++ b/tests/auth_test.ts @@ -14,6 +14,7 @@ */ import { + assertArrayIncludes, assertEquals, assertRejects, fail, @@ -39,6 +40,7 @@ import { import { assertErrorCode, cleanup, NatsServer, setup } from "./helpers/mod.ts"; import { deferred, + MsgImpl, NatsConnectionImpl, nkeys, } from "../nats-base-client/internal_mod.ts"; @@ -144,11 +146,11 @@ Deno.test("auth - sub no permissions keeps connection", async () => { }, { user: "a", pass: "a", reconnect: false }); const errStatus = deferred(); - const _ = (async () => { + (async () => { for await (const s of nc.status()) { errStatus.resolve(s); } - })(); + })().then(); const cbErr = deferred(); const sub = nc.subscribe("bar", { @@ -180,11 +182,11 @@ Deno.test("auth - sub iterator no permissions keeps connection", async () => { }, { user: "a", pass: "a", reconnect: false }); const errStatus = deferred(); - const _ = (async () => { + (async () => { for await (const s of nc.status()) { errStatus.resolve(s); } - })(); + })().then(); const iterErr = deferred(); const sub = nc.subscribe("bar"); @@ -222,11 +224,11 @@ Deno.test("auth - pub permissions keep connection", async () => { }, { user: "a", pass: "a", reconnect: false }); const errStatus = deferred(); - const _ = (async () => { + (async () => { for await (const s of nc.status()) { errStatus.resolve(s); } - })(); + })().then(); nc.publish("bar"); @@ -249,11 +251,11 @@ Deno.test("auth - req permissions keep connection", async () => { }, { user: "a", pass: "a", reconnect: false }); const errStatus = deferred(); - const _ = (async () => { + (async () => { for await (const s of nc.status()) { errStatus.resolve(s); } - })(); + })().then(); const err = await assertRejects( async () => { @@ -1194,3 +1196,59 @@ Deno.test("auth - creds and un and pw and token", async () => { await nc.close(); await ns.stop(); }); + +Deno.test("auth - request context", async () => { + const { ns, nc } = await setup({ + accounts: { + S: { + users: [{ + user: "s", + password: "s", + permission: { + subscribe: ["q.>", "_INBOX.>"], + publish: "$SYS.REQ.USER.INFO", + allow_responses: true, + }, + }], + exports: [ + { service: "q.>" }, + ], + }, + A: { + users: [{ user: "a", password: "a" }], + imports: [ + { service: { subject: "q.>", account: "S" } }, + ], + }, + }, + }, { user: "s", pass: "s" }); + + const srv = await (nc as NatsConnectionImpl).context(); + assertEquals(srv.data.user, "s"); + assertEquals(srv.data.account, "S"); + assertArrayIncludes(srv.data.permissions?.publish?.allow || [], [ + "$SYS.REQ.USER.INFO", + ]); + assertArrayIncludes(srv.data.permissions?.subscribe?.allow || [], [ + "q.>", + "_INBOX.>", + ]); + assertEquals(srv.data.permissions?.responses?.max, 1); + + nc.subscribe("q.>", { + callback(err, msg) { + if (err) { + fail(err.message); + } + const info = (msg as MsgImpl).requestInfo(); + assertEquals(info?.acc, "A"); + msg.respond(); + }, + }); + + const a = await connect({ user: "a", pass: "a", port: ns.port }); + console.log(await (a as NatsConnectionImpl).context()); + await a.request("q.hello"); + + await cleanup(ns, nc, a); +});