@soulcode/bexio-oauth 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 +66 -2
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +4 -0
- package/dist/bin.js.map +1 -0
- package/dist/claims.d.ts +31 -0
- package/dist/claims.d.ts.map +1 -0
- package/dist/claims.js +58 -0
- package/dist/claims.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +44 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +140 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +635 -0
- package/dist/client.js.map +1 -0
- package/dist/crypto.d.ts +27 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +101 -0
- package/dist/crypto.js.map +1 -0
- package/dist/duration.d.ts +4 -0
- package/dist/duration.d.ts.map +1 -0
- package/dist/duration.js +15 -0
- package/dist/duration.js.map +1 -0
- package/dist/errors.d.ts +38 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +13 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/permissions.d.ts +9 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/permissions.js +29 -0
- package/dist/permissions.js.map +1 -0
- package/dist/sql.d.ts +9 -0
- package/dist/sql.d.ts.map +1 -0
- package/dist/sql.js +15 -0
- package/dist/sql.js.map +1 -0
- package/dist/store/memory.d.ts +7 -0
- package/dist/store/memory.d.ts.map +1 -0
- package/dist/store/memory.js +104 -0
- package/dist/store/memory.js.map +1 -0
- package/dist/store/postgres.d.ts +17 -0
- package/dist/store/postgres.d.ts.map +1 -0
- package/dist/store/postgres.js +168 -0
- package/dist/store/postgres.js.map +1 -0
- package/dist/store/types.d.ts +105 -0
- package/dist/store/types.d.ts.map +1 -0
- package/dist/store/types.js +2 -0
- package/dist/store/types.js.map +1 -0
- package/package.json +58 -4
- package/sql/0001_bexio_grants.sql +32 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Soulcode
|
|
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,67 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @soulcode/bexio-oauth
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Bexio OAuth token custody for Node apps. Connect Bexio accounts, store their tokens encrypted in Postgres, and get a valid access token anywhere, including background jobs, without two server instances ever refreshing the same token at once.
|
|
4
|
+
|
|
5
|
+
It is **not** an auth framework: your app keeps its own login. For "Sign in with Bexio", let your auth framework run the login and hand the tokens over with `saveGrant()`.
|
|
6
|
+
|
|
7
|
+
- Full guide: [docs/integration.md](https://github.com/SoulcodeAgency/bexio-oauth/blob/main/docs/integration.md)
|
|
8
|
+
- Working examples (Better Auth, Supabase Auth): [examples/](https://github.com/SoulcodeAgency/bexio-oauth/tree/main/examples)
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @soulcode/bexio-oauth pg
|
|
14
|
+
npx @soulcode/bexio-oauth sql > migrations/0001_bexio_grants.sql # then apply it
|
|
15
|
+
npx @soulcode/bexio-oauth keygen # encryption key
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Requires Node ≥ 24 and PostgreSQL.
|
|
19
|
+
|
|
20
|
+
## Quickstart
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
// lib/bexio.ts (server-only)
|
|
24
|
+
import { Pool } from "pg";
|
|
25
|
+
import { createBexioOAuth, postgresStore } from "@soulcode/bexio-oauth";
|
|
26
|
+
|
|
27
|
+
export const bexio = createBexioOAuth({
|
|
28
|
+
clientId: process.env.BEXIO_CLIENT_ID!,
|
|
29
|
+
clientSecret: process.env.BEXIO_CLIENT_SECRET!,
|
|
30
|
+
redirectUri: process.env.BEXIO_REDIRECT_URI!, // e.g. https://app.example.com/api/bexio/callback
|
|
31
|
+
scopes: ["contact_show"],
|
|
32
|
+
keys: JSON.parse(process.env.BEXIO_TOKEN_KEYS!), // {"current":"k1","keys":{"k1":"<keygen>"}}
|
|
33
|
+
store: postgresStore(
|
|
34
|
+
new Pool({ connectionString: process.env.DATABASE_URL }),
|
|
35
|
+
),
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
// GET /api/bexio/connect
|
|
41
|
+
export const GET = async (req: Request) =>
|
|
42
|
+
bexio.start(req, {
|
|
43
|
+
ownerId: await currentWorkspaceId(req),
|
|
44
|
+
returnTo: "/settings",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// GET /api/bexio/callback (your redirectUri)
|
|
48
|
+
export const GET = async (req: Request) =>
|
|
49
|
+
bexio.callback(req, { ownerId: await currentWorkspaceId(req) });
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
// anywhere on the server, including cron jobs
|
|
54
|
+
const { accessToken } = await bexio.getAccessToken({ ownerId: workspaceId });
|
|
55
|
+
await fetch("https://api.bexio.com/2.0/contact", {
|
|
56
|
+
headers: {
|
|
57
|
+
Authorization: `Bearer ${accessToken}`,
|
|
58
|
+
Accept: "application/json",
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Also: `listGrants`, `disconnect`, `refreshIdle` (keep idle grants alive), `saveGrant` (tokens from your auth framework). Every error is a `BexioOAuthError` with a stable `code`.
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
MIT
|
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
|
package/dist/bin.js
ADDED
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B,OAAO,CAAC,QAAQ,GAAG,MAAM,GAAG,CAC1B,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EACrB,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAC9B,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAC/B,CAAC"}
|
package/dist/claims.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Bexio's production OIDC issuer (Keycloak realm). */
|
|
2
|
+
export declare const BEXIO_ISSUER = "https://auth.bexio.com/realms/bexio";
|
|
3
|
+
/** Bexio's production API base URL. */
|
|
4
|
+
export declare const BEXIO_API_BASE_URL = "https://api.bexio.com";
|
|
5
|
+
/**
|
|
6
|
+
* Always requested. `company_profile` adds the company claims; `offline_access`
|
|
7
|
+
* issues a refresh token that keeps working without the user present.
|
|
8
|
+
*/
|
|
9
|
+
export declare const BASE_SCOPES: string[];
|
|
10
|
+
export type ClaimBag = Record<string, unknown>;
|
|
11
|
+
/** Who granted access, as read from Bexio's ID token, access token and userinfo. */
|
|
12
|
+
export interface BexioIdentity {
|
|
13
|
+
/** OIDC `sub`. */
|
|
14
|
+
userId: string;
|
|
15
|
+
/** `company_id` (company_profile scope). */
|
|
16
|
+
companyId: string;
|
|
17
|
+
companyName: string | null;
|
|
18
|
+
/** `company_user_id` (access token): the user's id inside the company. */
|
|
19
|
+
companyUserId: number | null;
|
|
20
|
+
userName: string | null;
|
|
21
|
+
userEmail: string | null;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Maps a merged claim bag (ID token + access token + userinfo) to a
|
|
25
|
+
* `BexioIdentity`. Throws `invalid_callback` when `sub` or `company_id` is
|
|
26
|
+
* missing.
|
|
27
|
+
*/
|
|
28
|
+
export declare function mapClaims(claims: ClaimBag): BexioIdentity;
|
|
29
|
+
/** The value a grant's ciphertext is bound to (see `TokenCipher`). */
|
|
30
|
+
export declare function grantBinding(ownerId: string, companyId: string, userId: string): string;
|
|
31
|
+
//# sourceMappingURL=claims.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claims.d.ts","sourceRoot":"","sources":["../src/claims.ts"],"names":[],"mappings":"AAEA,uDAAuD;AACvD,eAAO,MAAM,YAAY,wCAAwC,CAAC;AAClE,uCAAuC;AACvC,eAAO,MAAM,kBAAkB,0BAA0B,CAAC;AAC1D;;;GAGG;AACH,eAAO,MAAM,WAAW,UAMvB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE/C,oFAAoF;AACpF,MAAM,WAAW,aAAa;IAC5B,kBAAkB;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,0EAA0E;IAC1E,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAaD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,QAAQ,GAAG,aAAa,CAwBzD;AAED,sEAAsE;AACtE,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,MAAM,CAER"}
|
package/dist/claims.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { BexioOAuthError } from "./errors.js";
|
|
2
|
+
/** Bexio's production OIDC issuer (Keycloak realm). */
|
|
3
|
+
export const BEXIO_ISSUER = "https://auth.bexio.com/realms/bexio";
|
|
4
|
+
/** Bexio's production API base URL. */
|
|
5
|
+
export const BEXIO_API_BASE_URL = "https://api.bexio.com";
|
|
6
|
+
/**
|
|
7
|
+
* Always requested. `company_profile` adds the company claims; `offline_access`
|
|
8
|
+
* issues a refresh token that keeps working without the user present.
|
|
9
|
+
*/
|
|
10
|
+
export const BASE_SCOPES = [
|
|
11
|
+
"openid",
|
|
12
|
+
"profile",
|
|
13
|
+
"email",
|
|
14
|
+
"company_profile",
|
|
15
|
+
"offline_access",
|
|
16
|
+
];
|
|
17
|
+
function text(value) {
|
|
18
|
+
if (typeof value === "string" && value.length > 0)
|
|
19
|
+
return value;
|
|
20
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
21
|
+
return String(value);
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
function integer(value) {
|
|
25
|
+
const parsed = typeof value === "string" ? Number.parseInt(value, 10) : value;
|
|
26
|
+
return typeof parsed === "number" && Number.isInteger(parsed) ? parsed : null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Maps a merged claim bag (ID token + access token + userinfo) to a
|
|
30
|
+
* `BexioIdentity`. Throws `invalid_callback` when `sub` or `company_id` is
|
|
31
|
+
* missing.
|
|
32
|
+
*/
|
|
33
|
+
export function mapClaims(claims) {
|
|
34
|
+
const userId = text(claims.sub);
|
|
35
|
+
if (!userId)
|
|
36
|
+
throw new BexioOAuthError("invalid_callback", "missing sub claim");
|
|
37
|
+
const companyId = text(claims.company_id);
|
|
38
|
+
if (!companyId) {
|
|
39
|
+
throw new BexioOAuthError("invalid_callback", "missing company_id claim (is the company_profile scope requested?)");
|
|
40
|
+
}
|
|
41
|
+
const given = text(claims.given_name);
|
|
42
|
+
const family = text(claims.family_name);
|
|
43
|
+
return {
|
|
44
|
+
userId,
|
|
45
|
+
companyId,
|
|
46
|
+
companyName: text(claims.company_name),
|
|
47
|
+
companyUserId: integer(claims.company_user_id),
|
|
48
|
+
userName: given && family
|
|
49
|
+
? `${given} ${family}`
|
|
50
|
+
: (text(claims.name) ?? given ?? family),
|
|
51
|
+
userEmail: text(claims.email),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** The value a grant's ciphertext is bound to (see `TokenCipher`). */
|
|
55
|
+
export function grantBinding(ownerId, companyId, userId) {
|
|
56
|
+
return `${ownerId}\u0000${companyId}\u0000${userId}`;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=claims.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claims.js","sourceRoot":"","sources":["../src/claims.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,uDAAuD;AACvD,MAAM,CAAC,MAAM,YAAY,GAAG,qCAAqC,CAAC;AAClE,uCAAuC;AACvC,MAAM,CAAC,MAAM,kBAAkB,GAAG,uBAAuB,CAAC;AAC1D;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,QAAQ;IACR,SAAS;IACT,OAAO;IACP,iBAAiB;IACjB,gBAAgB;CACjB,CAAC;AAiBF,SAAS,IAAI,CAAC,KAAc;IAC1B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAChE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9E,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,OAAO,CAAC,KAAc;IAC7B,MAAM,MAAM,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAC9E,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AAChF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,MAAgB;IACxC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAChC,IAAI,CAAC,MAAM;QACT,MAAM,IAAI,eAAe,CAAC,kBAAkB,EAAE,mBAAmB,CAAC,CAAC;IACrE,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC1C,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,eAAe,CACvB,kBAAkB,EAClB,oEAAoE,CACrE,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IACxC,OAAO;QACL,MAAM;QACN,SAAS;QACT,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC;QACtC,aAAa,EAAE,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC;QAC9C,QAAQ,EACN,KAAK,IAAI,MAAM;YACb,CAAC,CAAC,GAAG,KAAK,IAAI,MAAM,EAAE;YACtB,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,MAAM,CAAC;QAC5C,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;KAC9B,CAAC;AACJ,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,YAAY,CAC1B,OAAe,EACf,SAAiB,EACjB,MAAc;IAEd,OAAO,GAAG,OAAO,SAAS,SAAS,SAAS,MAAM,EAAE,CAAC;AACvD,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAWA,wBAAsB,GAAG,CACvB,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,EACxB,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,GACvB,OAAO,CAAC,MAAM,CAAC,CAgCjB"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { generateKey } from "./crypto.js";
|
|
2
|
+
import { migrations } from "./sql.js";
|
|
3
|
+
const HELP = `Usage: bexio-oauth <command>
|
|
4
|
+
|
|
5
|
+
Commands:
|
|
6
|
+
sql [--from <n>] Print the schema SQL (all migrations, or from migration <n> on)
|
|
7
|
+
keygen Print a new random 32-byte encryption key (base64url)
|
|
8
|
+
help Show this help
|
|
9
|
+
`;
|
|
10
|
+
export async function run(argv, out, err) {
|
|
11
|
+
const [command, ...rest] = argv;
|
|
12
|
+
switch (command) {
|
|
13
|
+
case "sql": {
|
|
14
|
+
let from = 1;
|
|
15
|
+
if (rest[0] === "--from") {
|
|
16
|
+
from = Number.parseInt(rest[1] ?? "", 10);
|
|
17
|
+
if (!Number.isInteger(from) || from < 1) {
|
|
18
|
+
err("--from needs a migration number, e.g. --from 2\n");
|
|
19
|
+
return 1;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else if (rest.length > 0) {
|
|
23
|
+
err(HELP);
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
26
|
+
const selected = (await migrations()).filter((m) => m.number >= from);
|
|
27
|
+
out(selected.map((m) => `-- ${m.name}\n${m.sql.trimEnd()}\n`).join("\n"));
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
case "keygen":
|
|
31
|
+
out(`${generateKey()}\n`);
|
|
32
|
+
return 0;
|
|
33
|
+
case undefined:
|
|
34
|
+
case "help":
|
|
35
|
+
case "--help":
|
|
36
|
+
case "-h":
|
|
37
|
+
out(HELP);
|
|
38
|
+
return 0;
|
|
39
|
+
default:
|
|
40
|
+
err(`Unknown command: ${command}\n\n${HELP}`);
|
|
41
|
+
return 1;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,MAAM,IAAI,GAAG;;;;;;CAMZ,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,GAAG,CACvB,IAAc,EACd,GAAwB,EACxB,GAAwB;IAExB,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAChC,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,KAAK,CAAC,CAAC,CAAC;YACX,IAAI,IAAI,GAAG,CAAC,CAAC;YACb,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACzB,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;gBAC1C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;oBACxC,GAAG,CAAC,kDAAkD,CAAC,CAAC;oBACxD,OAAO,CAAC,CAAC;gBACX,CAAC;YACH,CAAC;iBAAM,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC3B,GAAG,CAAC,IAAI,CAAC,CAAC;gBACV,OAAO,CAAC,CAAC;YACX,CAAC;YACD,MAAM,QAAQ,GAAG,CAAC,MAAM,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC;YACtE,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YAC1E,OAAO,CAAC,CAAC;QACX,CAAC;QACD,KAAK,QAAQ;YACX,GAAG,CAAC,GAAG,WAAW,EAAE,IAAI,CAAC,CAAC;YAC1B,OAAO,CAAC,CAAC;QACX,KAAK,SAAS,CAAC;QACf,KAAK,MAAM,CAAC;QACZ,KAAK,QAAQ,CAAC;QACd,KAAK,IAAI;YACP,GAAG,CAAC,IAAI,CAAC,CAAC;YACV,OAAO,CAAC,CAAC;QACX;YACE,GAAG,CAAC,oBAAoB,OAAO,OAAO,IAAI,EAAE,CAAC,CAAC;YAC9C,OAAO,CAAC,CAAC;IACb,CAAC;AACH,CAAC"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { type Keyring } from "./crypto.js";
|
|
2
|
+
import { type Duration } from "./duration.js";
|
|
3
|
+
import { type Role } from "./permissions.js";
|
|
4
|
+
import type { GrantRecord, GrantStore } from "./store/types.js";
|
|
5
|
+
export interface BexioOAuthConfig {
|
|
6
|
+
/** From your Bexio OAuth app. */
|
|
7
|
+
clientId: string;
|
|
8
|
+
clientSecret: string;
|
|
9
|
+
/** Must exactly match a redirect URI registered at Bexio. */
|
|
10
|
+
redirectUri: string;
|
|
11
|
+
/** Bexio API scopes your app needs; `BASE_SCOPES` are always added. */
|
|
12
|
+
scopes?: string[];
|
|
13
|
+
/** Encryption keyring (see `generateKey`). */
|
|
14
|
+
keys: Keyring;
|
|
15
|
+
store: GrantStore;
|
|
16
|
+
/** Defaults to Bexio's production issuer. Override only to point at a mock. */
|
|
17
|
+
issuer?: string;
|
|
18
|
+
/** Defaults to `https://api.bexio.com`. Override only to point at a mock. */
|
|
19
|
+
apiBaseUrl?: string;
|
|
20
|
+
/** How long one refresh may hold a grant's lock. Default `"30s"`. */
|
|
21
|
+
refreshLockTtl?: Duration;
|
|
22
|
+
/** Where `callback` redirects when the flow cookie is gone. Default `"/"`. */
|
|
23
|
+
defaultReturnTo?: string;
|
|
24
|
+
/** Custom `fetch` for every outgoing request (tests, proxies). */
|
|
25
|
+
fetch?: typeof fetch;
|
|
26
|
+
/** Clock override for tests. */
|
|
27
|
+
clock?: () => Date;
|
|
28
|
+
}
|
|
29
|
+
/** A grant as the app sees it: no token material. */
|
|
30
|
+
export interface Grant {
|
|
31
|
+
id: string;
|
|
32
|
+
ownerId: string;
|
|
33
|
+
companyId: string;
|
|
34
|
+
companyName: string | null;
|
|
35
|
+
userId: string;
|
|
36
|
+
userName: string | null;
|
|
37
|
+
userEmail: string | null;
|
|
38
|
+
companyUserId: number | null;
|
|
39
|
+
role: Role;
|
|
40
|
+
status: GrantRecord["status"];
|
|
41
|
+
scope: string | null;
|
|
42
|
+
accessExpiresAt: Date | null;
|
|
43
|
+
lastRefreshedAt: Date;
|
|
44
|
+
createdAt: Date;
|
|
45
|
+
updatedAt: Date;
|
|
46
|
+
}
|
|
47
|
+
/** Identifies one grant of an owner. */
|
|
48
|
+
export interface GrantSelector {
|
|
49
|
+
ownerId: string;
|
|
50
|
+
companyId?: string;
|
|
51
|
+
grantId?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface AccessToken {
|
|
54
|
+
accessToken: string;
|
|
55
|
+
expiresAt: Date | null;
|
|
56
|
+
grant: Grant;
|
|
57
|
+
}
|
|
58
|
+
/** Tokens obtained by your auth framework, for `saveGrant`. */
|
|
59
|
+
export interface ExternalTokens {
|
|
60
|
+
accessToken: string;
|
|
61
|
+
refreshToken?: string | null;
|
|
62
|
+
idToken?: string | null;
|
|
63
|
+
/** Access-token expiry; defaults to the access token's `exp` claim. */
|
|
64
|
+
expiresAt?: Date | null;
|
|
65
|
+
scope?: string | null;
|
|
66
|
+
}
|
|
67
|
+
export interface RefreshIdleResult {
|
|
68
|
+
refreshed: number;
|
|
69
|
+
/** Idle grants another process was refreshing at the same time. */
|
|
70
|
+
skipped: number;
|
|
71
|
+
reauthRequired: number;
|
|
72
|
+
failed: number;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* How the callback checks the owner. Either the `ownerId` your app derives
|
|
76
|
+
* from the signed-in session (must equal the one passed to `start`), or
|
|
77
|
+
* `authorizeOwner`, which receives the owner recorded at `start` and returns
|
|
78
|
+
* whether the signed-in user may connect Bexio for it. Use the latter when
|
|
79
|
+
* one callback URL serves many owners (e.g. workspaces).
|
|
80
|
+
*/
|
|
81
|
+
export type CallbackOptions = {
|
|
82
|
+
ownerId: string;
|
|
83
|
+
} | {
|
|
84
|
+
authorizeOwner: (ownerId: string) => boolean | Promise<boolean>;
|
|
85
|
+
};
|
|
86
|
+
export interface CallbackResult {
|
|
87
|
+
grant: Grant;
|
|
88
|
+
returnTo: string;
|
|
89
|
+
/** Contains the `Set-Cookie` that deletes the flow cookie; copy it onto your response. */
|
|
90
|
+
headers: Headers;
|
|
91
|
+
}
|
|
92
|
+
export type BexioOAuth = ReturnType<typeof createBexioOAuth>;
|
|
93
|
+
export declare function createBexioOAuth(config: BexioOAuthConfig): {
|
|
94
|
+
/**
|
|
95
|
+
* Starts the Connect flow: redirects to Bexio and sets the encrypted flow
|
|
96
|
+
* cookie. Derive `ownerId` from your authenticated session.
|
|
97
|
+
*/
|
|
98
|
+
start(_request: Request, options: {
|
|
99
|
+
ownerId: string;
|
|
100
|
+
returnTo?: string;
|
|
101
|
+
}): Promise<Response>;
|
|
102
|
+
/**
|
|
103
|
+
* Handles the redirect back from Bexio and saves the grant. Throws a
|
|
104
|
+
* `BexioOAuthError` on failure; use `callback` for a ready-made redirect.
|
|
105
|
+
*/
|
|
106
|
+
handleCallback(request: Request, options: CallbackOptions): Promise<CallbackResult>;
|
|
107
|
+
/**
|
|
108
|
+
* Handles the redirect back from Bexio, saves the grant, and redirects to
|
|
109
|
+
* `returnTo` (or `returnTo?bexio_error=<code>` on failure).
|
|
110
|
+
*/
|
|
111
|
+
callback(request: Request, options: CallbackOptions): Promise<Response>;
|
|
112
|
+
/**
|
|
113
|
+
* Saves tokens obtained by your auth framework ("Sign in with Bexio").
|
|
114
|
+
* Verifies the tokens' signatures against Bexio's keys first.
|
|
115
|
+
*/
|
|
116
|
+
saveGrant(input: {
|
|
117
|
+
ownerId: string;
|
|
118
|
+
tokens: ExternalTokens;
|
|
119
|
+
}): Promise<Grant>;
|
|
120
|
+
/**
|
|
121
|
+
* A valid access token for the owner's grant, refreshed when needed.
|
|
122
|
+
* Safe to call from background jobs and from many instances at once.
|
|
123
|
+
*/
|
|
124
|
+
getAccessToken(selector: GrantSelector): Promise<AccessToken>;
|
|
125
|
+
/** The owner's grants, without token material. */
|
|
126
|
+
listGrants(query: {
|
|
127
|
+
ownerId: string;
|
|
128
|
+
}): Promise<Grant[]>;
|
|
129
|
+
/** Revokes the refresh token at Bexio (best effort) and deletes the grant. */
|
|
130
|
+
disconnect(selector: GrantSelector): Promise<void>;
|
|
131
|
+
/**
|
|
132
|
+
* Refreshes active grants not refreshed for `idleFor`, so Bexio doesn't end
|
|
133
|
+
* their offline sessions. Run it on a schedule.
|
|
134
|
+
*/
|
|
135
|
+
refreshIdle(options: {
|
|
136
|
+
idleFor: Duration;
|
|
137
|
+
limit?: number;
|
|
138
|
+
}): Promise<RefreshIdleResult>;
|
|
139
|
+
};
|
|
140
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|