Skip to content

Commit

Permalink
[FEAT] added AuthenticationResponse (#22)
Browse files Browse the repository at this point in the history
[FIX] made user have GenericFields - for tags, etc
[FEAT] added ability to set issuer_account on generic claims
[TEST] added ability to set where nsc stores/reads files for cross lib checks
[DENO] added a deno.json/lock simplifying std imports
[LINT] fixed linter warnings
  • Loading branch information
aricart authored Mar 20, 2023
1 parent c92ef1f commit 41b87a7
Show file tree
Hide file tree
Showing 11 changed files with 453 additions and 27 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ experimental/
esm/jwt.js
cjs_src/
debug/
lib/
4 changes: 2 additions & 2 deletions bin/cjs-fix-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ requires.set(
);

// resolve the specified directories to fq
let dirs = (argv._ as string[]).map((n) => {
const dirs = (argv._ as string[]).map((n) => {
return resolve(n);
});
// resolve the out dir
Expand Down Expand Up @@ -88,7 +88,7 @@ await Deno.lstat(out)
// process each file - remove extensions from requires/import
for (const fn of files) {
const data = await Deno.readFile(fn);
let txt = new TextDecoder().decode(data);
const txt = new TextDecoder().decode(data);

let mod = txt.replace(/from\s+"(\S+).[t|j]s"/gim, 'from "$1"');
mod = mod.replace(/require\("(\S+).[j|t]s"\)/gim, 'require("$1")');
Expand Down
15 changes: 15 additions & 0 deletions deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"imports": {
"std/": "https://deno.land/std@0.177.0/"
},
"lint": {
"files": {
"exclude": ["docs/", "lib/", "esm/jwt.js", "debug/", "cjs/", "cjs_src/"]
}
},
"fmt": {
"files": {
"exclude": ["docs/", "lib/", "esm/jwt.js", "debug/", "cjs/", "cjs_src/"]
}
}
}
155 changes: 155 additions & 0 deletions deno.lock

Large diffs are not rendered by default.

39 changes: 38 additions & 1 deletion src/jwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import {
Account,
Activation,
AuthorizationResponse,
ClaimsData,
Generic,
Operator,
Expand Down Expand Up @@ -183,9 +184,45 @@ export async function encodeGeneric(
claim.name = name;
claim.nats = data;
claim.sub = akp.getPublicKey();
akp = checkKey(akp, "", !opts.signer);
let signer = akp;
if (opts.signer) {
signer = checkKey(opts.signer, "", true);
}
if (opts.signer) {
claim.nats.issuer_account = akp.getPublicKey();
}
const o = initAlgorithm(opts);
setVersionType(o.algorithm, kind, claim);
return await encode(o.algorithm, claim, akp);
return await encode(o.algorithm, claim, signer);
}

export async function encodeAuthorizationResponse(
user: Key,
server: Key,
issuer: Key,
data: Partial<AuthorizationResponse>,
opts: Partial<EncodingOptions>,
): Promise<string> {
opts = opts || {};
// this is only in v2
opts.algorithm = Algorithms.v2;
user = checkKey(user, "U", false);
server = checkKey(server, "N", false);
issuer = checkKey(issuer, "A", false);
let signer = issuer;
if (opts.signer) {
signer = checkKey(opts.signer, "A", true);
}
const claim = initClaim<AuthorizationResponse>(opts);
claim.sub = user.getPublicKey();
claim.aud = server.getPublicKey();
claim.nats = data;
if (opts.signer) {
claim.nats.issuer_account = issuer.getPublicKey();
}
setVersionType(opts.algorithm, Types.AuthorizationResponse, claim);
return await encode(opts.algorithm, claim, signer);
}

function setVersionType(
Expand Down
4 changes: 2 additions & 2 deletions src/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ export * from "./util.ts";
export {
createAccount,
createOperator,
createUser,
createServer,
fromSeed,
createUser,
fromPublic,
fromSeed,
} from "./nkeys.ts";
export type { KeyPair } from "./nkeys.ts";
9 changes: 8 additions & 1 deletion src/nkeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ const createUser = nkeys.createUser;
const createServer = nkeys.createServer;
const fromSeed = nkeys.fromSeed;
const fromPublic = nkeys.fromPublic;
export { createAccount, createOperator, createUser, createServer, fromPublic, fromSeed };
export {
createAccount,
createOperator,
createServer,
createUser,
fromPublic,
fromSeed,
};

export interface KeyPair {
getPublicKey(): string;
Expand Down
8 changes: 7 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export enum Types {
Account = "account",
User = "user",
Activation = "activation",
AuthorizationResponse = "authorization_response",
}

export interface NatsLimits {
Expand Down Expand Up @@ -99,7 +100,7 @@ export interface UserPermissionsLimits extends Permissions, Limits {
"allowed_connection_types": ConnectionType[];
}

export type User = UserPermissionsLimits & IssuerAccount;
export type User = UserPermissionsLimits & IssuerAccount & GenericFields;

export interface ValidDates {
exp?: number;
Expand Down Expand Up @@ -207,3 +208,8 @@ export interface ScopedUser extends GenericFields {
export interface IssuerAccount {
"issuer_account"?: string;
}

export interface AuthorizationResponse extends GenericFields, IssuerAccount {
error?: string;
jwt?: string;
}
54 changes: 47 additions & 7 deletions tests/jwt_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,29 @@

import {
assert,
assertArrayIncludes,
assertEquals,
assertExists,
assertRejects,
} from "https://deno.land/std@0.171.0/testing/asserts.ts";
} from "https://deno.land/std/testing/asserts.ts";
import { nsc, parseTable } from "./nsc.ts";
import {
Account,
Activation,
Algorithms,
AuthorizationResponse,
Base64UrlCodec,
ClaimsData,
createAccount,
createOperator,
createServer,
createUser,
decode,
defaultUserLimits,
defaultUserPermissionsLimits,
encodeAccount,
encodeActivation,
encodeAuthorizationResponse,
encodeGeneric,
encodeOperator,
encodeUser,
Expand Down Expand Up @@ -731,18 +735,54 @@ Deno.test("jwt - account disallow_bearer", async () => {

Deno.test("jwt - custom aud", async () => {
const akp = createAccount();
let at = await encodeAccount("A", akp, {}, { aud: "hello" });
let ac = await decode<Account>(at);
const at = await encodeAccount("A", akp, {}, { aud: "hello" });
const ac = await decode<Account>(at);
assertEquals(ac.aud, "hello");

const ukp = createUser();
let ut = await encodeUser("A", ukp, akp, defaultUserLimits(), {
const ut = await encodeUser("A", ukp, akp, defaultUserLimits(), {
aud: "hello",
});
let uc = await decode<User>(ut);
const uc = await decode<User>(ut);
assertEquals(uc.aud, "hello");

let gt = await encodeGeneric("A", akp, "my-kind", {}, { aud: "hello" });
let gc = await decode<unknown>(ut);
const gt = await encodeGeneric("A", akp, "my-kind", {}, { aud: "hello" });
const gc = await decode<unknown>(gt);
assertEquals(gc.aud, "hello");
});

Deno.test("jwt - tags", async () => {
const [o] = await nsc.addOperator();
await nsc.run("add", "account", "A");
await nsc.run("edit", "account", "--tag", "a", "--tag", "b", "--tag", "c");
const ac = await decode<Account>(await nsc.getAccount(o, "A"));
assertExists(ac.nats.tags);
assertArrayIncludes(ac.nats.tags, ["a", "b", "c"]);
ac.nats.tags.push("d");
assertArrayIncludes(ac.nats.tags, ["d"]);

await nsc.run("add", "user", "u");
await nsc.run("edit", "user", "--tag", "x", "--tag", "y", "--tag", "z");
const uc = await decode<User>(await nsc.getUser(o, "A", "u"));
assertExists(uc.nats.tags);
assertArrayIncludes(uc.nats.tags, ["x", "y", "z"]);
uc.nats.tags.push("zz");
assertArrayIncludes(uc.nats.tags, ["zz"]);
});

Deno.test("jwt - authorization response", async () => {
const user = createUser();
const server = createServer();
const account = createAccount();
const sk = createAccount();

const tok = await encodeAuthorizationResponse(user, server, account, {
jwt: "hello",
}, { signer: sk });
const ar = decode<AuthorizationResponse>(tok);
assertEquals(ar.sub, user.getPublicKey());
assertEquals(ar.aud, server.getPublicKey());
assertEquals(ar.iss, sk.getPublicKey());
assertEquals(ar.nats.issuer_account, account.getPublicKey());
assertEquals(ar.nats.jwt, "hello");
});
82 changes: 69 additions & 13 deletions tests/nsc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,73 @@
// limitations under the License.

import { dirname, join } from "https://deno.land/std/path/mod.ts";
import { ensureDir } from "https://deno.land/std@0.103.0/fs/mod.ts";
import { ensureDir } from "https://deno.land/std/fs/mod.ts";
import { assert } from "https://deno.land/std/testing/asserts.ts";
import { nuid } from "https://raw.githubusercontent.com/nats-io/nats.deno/main/nats-base-client/nuid.ts";
import type { KeyPair } from "../src/mod.ts";
import { Account, decode, fromSeed, Types } from "../src/mod.ts";
import RunOptions = Deno.RunOptions;

const root = await Deno.makeTempDir();
const storeDir = join(root, "store");
const keysDir = join(root, "keystore");
let root;

Deno.env.set("NKEYS_PATH", keysDir);
await run("env", "--store", storeDir);
if (!Deno.env.has("XDG_CONFIG_HOME")) {
root = await Deno.makeTempDir();
const config = join(root, "config");
Deno.env.set("XDG_CONFIG_HOME", config);
}
if (
Deno.env.has("XDG_DATA_HOME") === false
) {
root = root ?? await Deno.makeTempDir();
const storeDir = join(root, "data");
Deno.env.set("XDG_DATA_HOME", storeDir);
}

export function setNscData(p: string) {
Deno.env.set("XDG_DATA_HOME", p);
}

export function setNscConfig(p: string) {
Deno.env.set("XDG_CONFIG_HOME", p);
}

export function setNKeysDir(p: string) {
Deno.env.set("NKEYS_PATH", p);
}

export function getDataHome(): string {
const p = Deno.env.get("XDG_DATA_HOME") ?? "";
return p;
}

export function getConfigHome(): string {
return join(Deno.env.get("XDG_CONFIG_HOME") ?? "");
}

export function getKeysDir(): string {
if (Deno.env.has("NKEYS_PATH")) {
return Deno.env.get("NKEYS_PATH")!;
}
return join(getDataHome(), "nats", "nsc", "keys");
}

export function getStoresDir(): string {
const stores = join(
Deno.env.get("XDG_DATA_HOME") ?? "",
"nats",
"nsc",
"stores",
);
return stores;
}

export interface Std {
out: string;
err: string;
}

export interface Nsc {
env(): Promise<Std>;
addOperator(): Promise<[string, Std]>;
getOperator(n: string): Promise<string>;
getAccount(o: string, a: string): Promise<string>;
Expand All @@ -41,6 +89,9 @@ export interface Nsc {
}

export const nsc: Nsc = {
async env(): Promise<Std> {
return await run("env");
},
async addOperator(): Promise<[string, Std]> {
const name = nuid.next();
const std = await run("add", "operator", name);
Expand All @@ -56,7 +107,7 @@ export const nsc: Nsc = {
return Deno.readTextFile(userPath(o, a, u));
},
async findKeyPair(pk: string): Promise<KeyPair> {
const fp = join(keysDir, "keys", pk[0], pk.slice(1, 3), `${pk}.nk`);
const fp = join(getKeysDir(), "keys", pk[0], pk.slice(1, 3), `${pk}.nk`);
const seed = await Deno.readTextFile(fp);
return fromSeed(new TextEncoder().encode(seed));
},
Expand Down Expand Up @@ -126,31 +177,36 @@ export function parseTable(s: string): string[][] {
}

function operatorPath(n: string): string {
return join(storeDir, n, `${n}.jwt`);
const p = join(getStoresDir(), n, `${n}.jwt`);
return p;
}

function accountPath(o: string, a: string): string {
return join(storeDir, o, "accounts", a, `${a}.jwt`);
const p = join(getStoresDir(), o, "accounts", a, `${a}.jwt`);
return p;
}

function userPath(o: string, a: string, u: string): string {
return join(storeDir, o, "accounts", a, "users", `${u}.jwt`);
const p = join(getStoresDir(), o, "accounts", a, "users", `${u}.jwt`);
return p;
}

async function run(...args: string[]): Promise<Std> {
const cmd = [
Deno.env.get("CI") ? "/home/runner/work/jwt.js/jwt.js/nsc" : "nsc",
];
cmd.push(...args);
const nsc = Deno.run({
const opts: RunOptions = {
cmd: cmd,
stderr: "piped",
stdout: "piped",
stdin: "null",
env: {
NKEYS_PATH: keysDir,
XDG_DATA_HOME: getDataHome(),
XDG_CONFIG_HOME: getConfigHome(),
},
});
};
const nsc = Deno.run(opts);
const { success } = await nsc.status();
const out = new TextDecoder().decode(await nsc.output());
const err = new TextDecoder().decode(await nsc.stderrOutput());
Expand Down
Loading

0 comments on commit 41b87a7

Please sign in to comment.