@agentage/identity 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +30 -2
- package/dist/auth.d.ts +58 -0
- package/dist/auth.js +42 -0
- package/dist/identity.d.ts +33 -0
- package/dist/identity.js +65 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/package.json +44 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Agentage Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @agentage/identity
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The identity client for an agentage browser page: sign in at auth, a one-day token for the
|
|
4
|
+
parts, sign out. It runs in the page and talks to auth with credentialed requests.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import { createIdentity } from '@agentage/identity';
|
|
8
|
+
|
|
9
|
+
const id = createIdentity({
|
|
10
|
+
authUrl: 'https://auth.agentage.io',
|
|
11
|
+
audience: 'next.agentage.io',
|
|
12
|
+
scopes: ['probe:read'],
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
await id.signIn(); // auth's sign-in page, then back to this page
|
|
16
|
+
const token = id.token(); // a TokenSource for createBridge({ token })
|
|
17
|
+
await id.signOut(); // revokes the token, then ends the session
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- `signIn()` resolves with the account when a session exists. Without one, the page goes to
|
|
21
|
+
`<authUrl>/sign-in?next=<this page>` and comes back after sign-in; call `signIn()` again on load.
|
|
22
|
+
- `token()` returns `() => Promise<string | undefined>`. The first call mints a one-day PAT
|
|
23
|
+
(`POST /account/pat`, `format: 'jwt'`, label `browser`); it stays in page memory only and is
|
|
24
|
+
minted again after it expires. Without a session it gives `undefined`.
|
|
25
|
+
- `signOut()` deletes that PAT (`DELETE /account/pat/{id}`; the parts refuse it within 60 s),
|
|
26
|
+
then ends the auth session.
|
|
27
|
+
- `account()` is the signed-in account or `null`; `tokens()` lists the account's PATs.
|
|
28
|
+
|
|
29
|
+
Auth needs at least one scope to mint, so pass `scopes`. `audience` is sent with the mint; auth
|
|
30
|
+
derives the audience itself today. A failed call throws an `IdentityError` with `code`,
|
|
31
|
+
`message` and the HTTP `status`. The client never logs a token.
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as z from 'zod/mini';
|
|
2
|
+
export type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
|
|
3
|
+
export type IdentityError = Error & {
|
|
4
|
+
code: string;
|
|
5
|
+
status: number;
|
|
6
|
+
};
|
|
7
|
+
declare const User: z.ZodMiniObject<{
|
|
8
|
+
id: z.ZodMiniString<string>;
|
|
9
|
+
email: z.ZodMiniString<string>;
|
|
10
|
+
name: z.ZodMiniOptional<z.ZodMiniNullable<z.ZodMiniString<string>>>;
|
|
11
|
+
}, z.core.$strip>;
|
|
12
|
+
declare const Minted: z.ZodMiniObject<{
|
|
13
|
+
success: z.ZodMiniLiteral<true>;
|
|
14
|
+
data: z.ZodMiniObject<{
|
|
15
|
+
id: z.ZodMiniString<string>;
|
|
16
|
+
token: z.ZodMiniString<string>;
|
|
17
|
+
expiresAt: z.ZodMiniString<string>;
|
|
18
|
+
}, z.core.$strip>;
|
|
19
|
+
}, z.core.$strip>;
|
|
20
|
+
declare const Summary: z.ZodMiniObject<{
|
|
21
|
+
createdAt: z.ZodMiniNullable<z.ZodMiniString<string>>;
|
|
22
|
+
expiresAt: z.ZodMiniNullable<z.ZodMiniString<string>>;
|
|
23
|
+
id: z.ZodMiniString<string>;
|
|
24
|
+
label: z.ZodMiniString<string>;
|
|
25
|
+
scopes: z.ZodMiniArray<z.ZodMiniString<string>>;
|
|
26
|
+
}, z.core.$strip>;
|
|
27
|
+
export type Account = z.infer<typeof User>;
|
|
28
|
+
export type MintedPat = z.infer<typeof Minted>['data'];
|
|
29
|
+
export type TokenSummary = z.infer<typeof Summary>;
|
|
30
|
+
export declare const authCalls: (authUrl: string, fetchFn: FetchLike) => {
|
|
31
|
+
account: () => Promise<{
|
|
32
|
+
id: string;
|
|
33
|
+
email: string;
|
|
34
|
+
name?: string | null | undefined;
|
|
35
|
+
} | null>;
|
|
36
|
+
mint: (body: object) => Promise<{
|
|
37
|
+
id: string;
|
|
38
|
+
token: string;
|
|
39
|
+
expiresAt: string;
|
|
40
|
+
}>;
|
|
41
|
+
list: () => Promise<{
|
|
42
|
+
createdAt: string | null;
|
|
43
|
+
expiresAt: string | null;
|
|
44
|
+
id: string;
|
|
45
|
+
label: string;
|
|
46
|
+
scopes: string[];
|
|
47
|
+
}[]>;
|
|
48
|
+
revoke: (id: string) => Promise<{
|
|
49
|
+
success: true;
|
|
50
|
+
data: {
|
|
51
|
+
revoked: true;
|
|
52
|
+
};
|
|
53
|
+
}>;
|
|
54
|
+
end: () => Promise<{
|
|
55
|
+
success: true;
|
|
56
|
+
}>;
|
|
57
|
+
};
|
|
58
|
+
export {};
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as z from 'zod/mini';
|
|
2
|
+
const fail = ({ code, message }, status = 0) => Object.assign(new Error(message), { name: 'IdentityError', code, status });
|
|
3
|
+
const unreachable = (path) => () => Promise.reject(fail({ code: 'NETWORK', message: `auth unreachable: ${path}` }));
|
|
4
|
+
const ok = (data) => z.object({ success: z.literal(true), data });
|
|
5
|
+
const str = z.string();
|
|
6
|
+
const Failed = z.object({ error: z.object({ code: str, message: str }) });
|
|
7
|
+
const User = z.object({ id: str, email: str, name: z.optional(z.nullable(str)) });
|
|
8
|
+
const Minted = ok(z.object({ id: str, token: str, expiresAt: str }));
|
|
9
|
+
const Times = { createdAt: z.nullable(str), expiresAt: z.nullable(str) };
|
|
10
|
+
const Revoked = ok(z.object({ revoked: z.literal(true) }));
|
|
11
|
+
const Summary = z.object({ id: str, label: str, scopes: z.array(str), ...Times });
|
|
12
|
+
// One credentialed call to auth: the body parsed by `schema`, else a typed error.
|
|
13
|
+
const caller = (authUrl, fetchFn) => async (schema, path, init = {}) => {
|
|
14
|
+
const sent = { ...init, credentials: 'include' };
|
|
15
|
+
const res = await fetchFn(`${authUrl}${path}`, sent).catch(unreachable(path));
|
|
16
|
+
const body = await res.json().catch(() => undefined);
|
|
17
|
+
const parsed = schema.safeParse(body);
|
|
18
|
+
if (res.ok && parsed.success)
|
|
19
|
+
return parsed.data;
|
|
20
|
+
const plain = { code: `HTTP_${res.status}`, message: `${path} answered ${res.status}` };
|
|
21
|
+
throw fail(Failed.safeParse(body).data?.error ?? plain, res.status);
|
|
22
|
+
};
|
|
23
|
+
const JSON_TYPE = { 'content-type': 'application/json' };
|
|
24
|
+
const SIGN_OUT = { method: 'POST', headers: JSON_TYPE, body: '{}' };
|
|
25
|
+
// auth's REST: the session by cookie, the PAT routes by a short-lived session JWT.
|
|
26
|
+
export const authCalls = (authUrl, fetchFn) => {
|
|
27
|
+
const call = caller(authUrl, fetchFn);
|
|
28
|
+
const pat = async (schema, path, method, body) => {
|
|
29
|
+
const { token } = await call(z.object({ token: str }), '/api/auth/token');
|
|
30
|
+
const headers = { ...JSON_TYPE, authorization: `Bearer ${token}` };
|
|
31
|
+
return call(schema, `/account/pat${path}`, { method, headers, body: JSON.stringify(body) });
|
|
32
|
+
};
|
|
33
|
+
const session = z.nullable(z.object({ user: User }));
|
|
34
|
+
return {
|
|
35
|
+
// get-session answers 200 and null when nobody is signed in.
|
|
36
|
+
account: async () => (await call(session, '/api/auth/get-session'))?.user ?? null,
|
|
37
|
+
mint: async (body) => (await pat(Minted, '', 'POST', body)).data,
|
|
38
|
+
list: async () => (await pat(ok(z.array(Summary)), '', 'GET')).data,
|
|
39
|
+
revoke: (id) => pat(Revoked, `/${encodeURIComponent(id)}`, 'DELETE'),
|
|
40
|
+
end: () => call(z.object({ success: z.literal(true) }), '/api/auth/sign-out', SIGN_OUT),
|
|
41
|
+
};
|
|
42
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type FetchLike } from './auth.js';
|
|
2
|
+
export type TokenSource = () => Promise<string | undefined>;
|
|
3
|
+
export type Identity = ReturnType<typeof createIdentity>;
|
|
4
|
+
type Nav = Pick<Location, 'href' | 'assign'>;
|
|
5
|
+
export interface IdentityOptions {
|
|
6
|
+
authUrl: string;
|
|
7
|
+
audience: string;
|
|
8
|
+
scopes?: string[];
|
|
9
|
+
fetch?: FetchLike;
|
|
10
|
+
location?: Nav;
|
|
11
|
+
}
|
|
12
|
+
export declare const createIdentity: (options: IdentityOptions) => {
|
|
13
|
+
account: () => Promise<{
|
|
14
|
+
id: string;
|
|
15
|
+
email: string;
|
|
16
|
+
name?: string | null | undefined;
|
|
17
|
+
} | null>;
|
|
18
|
+
signIn: () => Promise<{
|
|
19
|
+
id: string;
|
|
20
|
+
email: string;
|
|
21
|
+
name?: string | null | undefined;
|
|
22
|
+
}>;
|
|
23
|
+
token: () => TokenSource;
|
|
24
|
+
tokens: () => Promise<{
|
|
25
|
+
createdAt: string | null;
|
|
26
|
+
expiresAt: string | null;
|
|
27
|
+
id: string;
|
|
28
|
+
label: string;
|
|
29
|
+
scopes: string[];
|
|
30
|
+
}[]>;
|
|
31
|
+
signOut: () => Promise<void>;
|
|
32
|
+
};
|
|
33
|
+
export {};
|
package/dist/identity.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { authCalls } from './auth.js';
|
|
2
|
+
// A token this close to its expiry is minted again, so a call never carries a dying one.
|
|
3
|
+
const SKEW_MS = 60_000;
|
|
4
|
+
const DAY_MS = 86_400_000;
|
|
5
|
+
// From the local mint time, so a client clock off from auth's cannot force a mint per call.
|
|
6
|
+
const deadline = (pat, startedAt) => {
|
|
7
|
+
const life = Date.parse(pat.expiresAt) - startedAt;
|
|
8
|
+
return startedAt + (life > SKEW_MS ? Math.min(life, DAY_MS) : DAY_MS) - SKEW_MS;
|
|
9
|
+
};
|
|
10
|
+
// The PAT lives in page memory only: one mint in flight, again once it expires.
|
|
11
|
+
const tokenCache = (renew) => {
|
|
12
|
+
let held;
|
|
13
|
+
let minting;
|
|
14
|
+
const mint = async () => {
|
|
15
|
+
const startedAt = Date.now();
|
|
16
|
+
const pat = await renew();
|
|
17
|
+
held = pat && { pat, until: deadline(pat, startedAt) };
|
|
18
|
+
};
|
|
19
|
+
const current = async () => {
|
|
20
|
+
if (!held || held.until <= Date.now())
|
|
21
|
+
await (minting ??= mint().finally(() => (minting = undefined)));
|
|
22
|
+
return held?.pat.token;
|
|
23
|
+
};
|
|
24
|
+
// A mint in flight lands first, so its PAT is revoked too.
|
|
25
|
+
const release = async (revoke) => {
|
|
26
|
+
await minting?.catch(() => undefined);
|
|
27
|
+
const last = held;
|
|
28
|
+
held = undefined;
|
|
29
|
+
if (last)
|
|
30
|
+
await revoke(last.pat.id);
|
|
31
|
+
};
|
|
32
|
+
return { current, release };
|
|
33
|
+
};
|
|
34
|
+
// Signed in: resolves now. Else the page leaves for auth's sign-in and comes back to `next`.
|
|
35
|
+
const signIn = async (auth, authUrl, nav) => {
|
|
36
|
+
const signedIn = await auth.account();
|
|
37
|
+
if (signedIn)
|
|
38
|
+
return signedIn;
|
|
39
|
+
const page = nav ?? globalThis.location;
|
|
40
|
+
page.assign(`${authUrl}/sign-in?next=${encodeURIComponent(page.href)}`);
|
|
41
|
+
return new Promise(() => undefined);
|
|
42
|
+
};
|
|
43
|
+
// The session ends even when the revoke fails; the failure still reaches the caller.
|
|
44
|
+
const signOut = async (auth, cache) => {
|
|
45
|
+
try {
|
|
46
|
+
await cache.release(auth.revoke);
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
await auth.end();
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
export const createIdentity = (options) => {
|
|
53
|
+
const authUrl = options.authUrl.replace(/\/+$/, '');
|
|
54
|
+
const auth = authCalls(authUrl, options.fetch ?? ((url, init) => fetch(url, init)));
|
|
55
|
+
const { scopes, audience } = options;
|
|
56
|
+
const body = { format: 'jwt', label: 'browser', expiresInDays: 1, scopes, audience };
|
|
57
|
+
const cache = tokenCache(async () => ((await auth.account()) ? auth.mint(body) : undefined));
|
|
58
|
+
return {
|
|
59
|
+
account: auth.account,
|
|
60
|
+
signIn: () => signIn(auth, authUrl, options.location),
|
|
61
|
+
token: () => cache.current,
|
|
62
|
+
tokens: auth.list,
|
|
63
|
+
signOut: () => signOut(auth, cache),
|
|
64
|
+
};
|
|
65
|
+
};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './identity.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentage/identity",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The agentage identity client for browser pages: sign in at auth, a one-day token for the parts, sign out",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/agentage/platform.git",
|
|
10
|
+
"directory": "packages/identity"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://agentage.io",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/agentage/platform/issues"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=22"
|
|
21
|
+
},
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"default": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./package.json": "./package.json"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"!dist/**/*.test.*"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc -b",
|
|
35
|
+
"verify": "npm run verify --prefix ../..",
|
|
36
|
+
"prepublishOnly": "npm run verify"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"zod": "^4.2.0"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"agentage",
|
|
43
|
+
"extension",
|
|
44
|
+
"identity"
|
|
45
|
+
]
|
|
46
|
+
}
|