Skip to content

Commit

Permalink
Feat: Update to the interface and type checks
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielRivers committed Oct 20, 2024
1 parent e4b4d50 commit da185b6
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 93 deletions.
59 changes: 55 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Description

Types and methods to work with Kinde Infrastructure features like workflows
Types and methods to work with Kinde Infrastructure features

## Installation

Expand All @@ -19,11 +19,62 @@ pnpm install @kinde/infrastructure

### Methods

`getKindeIdTokenHandle` - Returns mutatable ID token object
`idTokenCustomClaims` - Returns mutatable ID token object

`getKindeAccessTokenHandle` - Returns mutatable access token object
`accessCustomClaims` - Returns mutatable access token object

`m2mCustomClaims` - Returns mutatable M2M token object

### Workflow Event

```js
{
request: {
ipAddress // IP address of where the workflow was triggered from
};
context: {
workflowTrigger // This is the type of the trigger
clientId // this will contain the source application ID the workflow was triggered from
orgCode // Organisation code the user is logging into
userId // this is the user ID e.g. kp_XXXX
clientId // client id of the origin
}
};
```

### Example

```ts
import { onUserTokenGeneratedEvent, getKindeAccessTokenHandle, WorkflowSettings, WorkflowTrigger } from "@kinde/infrastructure"

export const workflowSettings: WorkflowSettings = {
id: "addAccessTokenClaim",
trigger: WorkflowTrigger.UserTokenGeneration,
};

export default {
async handle(event: onUserTokenGeneratedEvent) {
const accessToken = accessCustomClaims<{ hello: string; ipAddress: string}>();
accessToken.hello = "Hello there!";
accessToken.ipAddress = event.request.ipAddress
},
};
```

This will result with two new extra claims added to the AccessToken

```json
{
"hello": "Hello there!",
"ipAddress": "1.2.3.4"
}
```

> [!WARNING]
> Some claims are prohibited to be updated from a workflow. (see [Prohibited Claims](/lib/prohibitedClaims.ts))
>
> No sensitive data should be added to tokens as these can be accessed publically
`getKindeM2MTokenHandle` - Returns mutatable M2M token object

## Kinde documentation

Expand Down
28 changes: 14 additions & 14 deletions lib/main.test.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,50 @@
import { describe, it, expect } from "vitest";
import { getKindeIdTokenHandle, getKindeAccessTokenHandle } from "./main";
import { describe, it, expect, vi } from "vitest";
import { idTokenCustomClaims, accessTokenCustomClaims } from "./main";
import { get } from "http";

global.kinde = {
idToken: {
value: {
sub: "123",
},
getCustomClaims: vi.fn().mockReturnValue({ }),
setCustomClaim: vi.fn(),
},
accessToken: {
value: {
sub: "654",
},
getCustomClaims: vi.fn().mockReturnValue({ }),
setCustomClaim: vi.fn(),

},
};

describe("ID Token", () => {
it("should return a proxy object with IdToken properties", () => {
const idTokenHandle = getKindeIdTokenHandle();
const idTokenHandle = idTokenCustomClaims();
expect(idTokenHandle).toHaveProperty("sub");
expect(idTokenHandle.sub).toBe("123");
});

it("should not allow overriding of sub", () => {
const idTokenHandle = getKindeIdTokenHandle();
const idTokenHandle = idTokenCustomClaims();
expect(() => {
idTokenHandle.sub = "456";
}).toThrowError("Access to sub is not allowed");
expect(idTokenHandle.sub).toBe("123");
});

it("should allow setting of custom property", () => {
const idTokenHandle = getKindeIdTokenHandle<{ ipAddress: string }>();
const idTokenHandle = idTokenCustomClaims<{ ipAddress: string }>();
idTokenHandle.ipAddress = "1.2.3.4";
expect(idTokenHandle.ipAddress).toBe("1.2.3.4");
});
});

describe("Access Token", () => {
it("should return a proxy object with AccessToken properties", () => {
const accessTokenHandle = getKindeAccessTokenHandle();
const accessTokenHandle = accessTokenCustomClaims();
expect(accessTokenHandle).toHaveProperty("sub");
expect(accessTokenHandle.sub).toBe("654");
});

it("should not allow overriding of sub", () => {
const accessTokenHandle = getKindeAccessTokenHandle();
const accessTokenHandle = accessTokenCustomClaims();

expect(() => {
accessTokenHandle.sub = "456";
Expand All @@ -53,7 +53,7 @@ describe("Access Token", () => {
});

it("should allow setting of custom property", () => {
const accessTokenHandle = getKindeIdTokenHandle<{ ipAddress: string }>();
const accessTokenHandle = idTokenCustomClaims<{ ipAddress: string }>();
accessTokenHandle.ipAddress = "1.2.3.4";
expect(accessTokenHandle.ipAddress).toBe("1.2.3.4");
});
Expand Down
121 changes: 61 additions & 60 deletions lib/main.ts
Original file line number Diff line number Diff line change
@@ -1,101 +1,102 @@
import { IdToken, AccessToken, M2MToken } from "./types";
export * from "./types";
import { KindeIdTokenProhibitedClaims, KindeAccessTokenProhibitedClaims, Kindem2mTokenProhibitedClaims } from "./prohibitedClaims.ts";

const commonProhibitedClaims = [
"azp",
"exp",
"iat",
"iss",
"nbf",
"sid",
"sub",
"act",
"iss",
"sid",
"aud",
];
const KindeIdTokenProhibitedClaims = [
...commonProhibitedClaims,
"auth_time,jti,updated_at,rat",
];
const KindeAccessTokenProhibitedClaims = [
...commonProhibitedClaims,
"jti",
"scp",
];
const Kindem2mTokenProhibitedClaims = [
...commonProhibitedClaims,
"gty",
"gty",
"jti",
"scp",
];
declare namespace kinde {
export function fetch(url: string, options: unknown): Promise<any>;

const idTokenProxyHandler = {
namespace env {
export function get(key: string): string;
}

namespace idToken {
export function setCustomClaim(key: string, value: unknown): void;
export function getCustomClaims(): unknown;
}
namespace accessToken {
export function setCustomClaim(key: string, value: unknown): void;
export function getCustomClaims(): unknown;
}
namespace m2mToken {
export function setCustomClaim(key: string, value: unknown): void;
export function getCustomClaims(): unknown;
}

namespace auth {
export function denyAccess(reason: string): void;
}

namespace risk {
export function setScore(score: number): void;
export function getScore(): number;
}
}

const idTokenClaimsHandler = {
get(target: any, prop: string, receiver: any) {
return Reflect.get(target, prop.toString(), receiver);
},
set(target: any, prop: string, receiver: any) {
if (KindeIdTokenProhibitedClaims.includes(prop.toString())) {
throw new Error(`Access to ${prop.toString()} is not allowed`);
}
kinde.idToken.setCustomClaim(prop, receiver);
return Reflect.set(target, prop, receiver);
},
};

const accessTokenProxyHandler = {
const accessTokenClaimsHandler = {
get(target: any, prop: string, receiver: any) {
return Reflect.get(target, prop.toString(), receiver);
},
set(target: any, prop: string, receiver: any) {
if (KindeAccessTokenProhibitedClaims.includes(prop.toString())) {
throw new Error(`Access to ${prop.toString()} is not allowed`);
}
kinde.accessToken.setCustomClaim(prop, receiver);
return Reflect.set(target, prop, receiver);
},
};

const m2mTokenProxyHandler = {
const m2mTokenClaimsHandler = {
get(target: any, prop: string, receiver: any) {
return Reflect.get(target, prop.toString(), receiver);
},
set(target: any, prop: string, receiver: any) {
if (Kindem2mTokenProhibitedClaims.includes(prop.toString())) {
throw new Error(`Access to ${prop.toString()} is not allowed`);
}
kinde.idToken.setCustomClaim(prop, receiver);
return Reflect.set(target, prop, receiver);
},
};

/**
* Returns mutatable ID Token object
*/
export function getKindeIdTokenHandle<T>(): T & IdToken {
return new Proxy<T & IdToken>(
//@ts-expect-error This is injected at runtime
kinde.idToken.value,
idTokenProxyHandler,
);
export function idTokenCustomClaims<T extends object>(): Omit<T, KindeIdTokenProhibitedClaims> {
const claims = kinde.idToken.getCustomClaims() as Omit<T, KindeIdTokenProhibitedClaims>;
return new Proxy<Omit<T, KindeIdTokenProhibitedClaims>>(claims, idTokenClaimsHandler);
}

/**
* Returns mutatable access token object
*/
export function getKindeAccessTokenHandle<T>(): T & AccessToken {
return new Proxy<T & AccessToken>(
//@ts-expect-error This is injected at runtime
kinde.accessToken.value,
accessTokenProxyHandler,
);
export function accessTokenCustomClaims<T extends object>(): Omit<T, KindeAccessTokenProhibitedClaims> {
const claims = kinde.accessToken.getCustomClaims() as Omit<T, KindeAccessTokenProhibitedClaims>;
return new Proxy<Omit<T, KindeAccessTokenProhibitedClaims>>(claims, accessTokenClaimsHandler);
}

/**
* Returns mutatable M2M token object
*/
export function getKindeM2MTokenHandle<T>(): T & M2MToken {
return new Proxy<T & AccessToken>(
//@ts-expect-error This is injected at runtime
kinde.m2mToken.value,
m2mTokenProxyHandler,
);
export function m2mTokenClaims<T extends object>(): Omit<T, Kindem2mTokenProhibitedClaims> {
const claims = kinde.accessToken.getCustomClaims() as Omit<T, Kindem2mTokenProhibitedClaims>;
return new Proxy<Omit<T, Kindem2mTokenProhibitedClaims>>(claims, m2mTokenClaimsHandler);
}

/**
* Gets the environment variable from the Kinde buisness dashboard
* @param key
*/
export function getEnvironmentVariable<T = string>(key: T): string {
return kinde.env.get(key as string);
}

/**
* Deny access to the user
* @param reason Reason for denying access
*/
export function denyAccess(reason: string) {
kinde.auth.denyAccess(reason);
}
14 changes: 14 additions & 0 deletions lib/prohibitedClaims.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export type CommonProhibitedClaims =
| "azp"
| "exp"
| "iat"
| "iss"
| "nbf"
| "sid"
| "sub"
| "act"
| "aud";

export type KindeIdTokenProhibitedClaims = CommonProhibitedClaims | "auth_time" | "jti" | "updated_at" | "rat";
export type KindeAccessTokenProhibitedClaims = CommonProhibitedClaims | "jti" | "scp";
export type Kindem2mTokenProhibitedClaims = CommonProhibitedClaims | "gty" | "jti" | "scp";
36 changes: 22 additions & 14 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,25 +155,33 @@ export enum WorkflowTrigger {
}

type EventBase = {
request: {
auth: {
audience: string[];
};
ip: string;
};
request: RequestContext,
context: {
user: {};
org: {};
app: {
clientId: string;
};
};
clientId: string;
orgCode: string;
}
};

type RequestContext = {
auth: {
audience: string[];
scope: string[];
};
ipAddress: string;
}

export type onUserTokenGeneratedEvent = EventBase & {
trigger: WorkflowTrigger.UserTokenGeneration;
context: {
workflowTrigger: WorkflowTrigger.UserTokenGeneration;
userId: string;
};
};

export type onM2MTokenGeneratedEvent = EventBase & {
trigger: WorkflowTrigger.M2MTokenGeneration;
context: {
trigger: WorkflowTrigger.M2MTokenGeneration;
};
};

// export * from './prohibitedClaims';

5 changes: 4 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,8 @@
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src", "lib/main.ts", "lib/types.ts"]
"include": ["src", "lib/**/*.ts", "lib/types.ts"],
"exclude": [
"lib/**/*.test.ts"
]
}

0 comments on commit da185b6

Please sign in to comment.