@soulcode/bexio-oauth 0.2.0 → 0.3.1
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/README.md +8 -4
- package/dist/claims.d.ts +2 -2
- package/dist/claims.d.ts.map +1 -1
- package/dist/claims.js +3 -3
- package/dist/claims.js.map +1 -1
- package/dist/crypto.d.ts +2 -2
- package/dist/crypto.js +5 -5
- package/dist/crypto.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/grants.d.ts +20 -11
- package/dist/grants.d.ts.map +1 -1
- package/dist/grants.js +67 -39
- package/dist/grants.js.map +1 -1
- package/dist/idle-sweep.d.ts +1 -1
- package/dist/idle-sweep.d.ts.map +1 -1
- package/dist/idle-sweep.js +5 -5
- package/dist/idle-sweep.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/migrations.generated.d.ts.map +1 -1
- package/dist/migrations.generated.js +26 -10
- package/dist/migrations.generated.js.map +1 -1
- package/dist/refresh.d.ts +7 -7
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +38 -30
- package/dist/refresh.js.map +1 -1
- package/dist/store/memory.d.ts.map +1 -1
- package/dist/store/memory.js +82 -46
- package/dist/store/memory.js.map +1 -1
- package/dist/store/postgres.d.ts +3 -3
- package/dist/store/postgres.d.ts.map +1 -1
- package/dist/store/postgres.js +128 -59
- package/dist/store/postgres.js.map +1 -1
- package/dist/store/types.d.ts +61 -21
- package/dist/store/types.d.ts.map +1 -1
- package/dist/types.d.ts +24 -10
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/sql/0001_bexio_oauth.sql +48 -0
- package/sql/0001_bexio_grants.sql +0 -32
package/dist/store/postgres.js
CHANGED
|
@@ -1,20 +1,53 @@
|
|
|
1
1
|
import { BexioOAuthError } from "../errors.js";
|
|
2
|
-
// `id`
|
|
2
|
+
// `id` columns are uuids: any other value matches no row, and would make
|
|
3
3
|
// Postgres reject the cast instead of returning nothing.
|
|
4
4
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
5
|
-
const
|
|
5
|
+
const CONNECTION_COLUMNS = `id, bexio_company_id, bexio_user_id, bexio_company_user_id,
|
|
6
6
|
company_name, user_name, user_email, role, status, access_token, refresh_token,
|
|
7
7
|
access_expires_at, refresh_expires_at, scope, refresh_locked_until,
|
|
8
|
-
last_refreshed_at, created_at, updated_at`;
|
|
8
|
+
authorized_at, last_refreshed_at, created_at, updated_at`;
|
|
9
|
+
/** Connection columns qualified with `c.`, for joins. */
|
|
10
|
+
const C_COLUMNS = CONNECTION_COLUMNS.split(",")
|
|
11
|
+
.map((column) => `c.${column.trim()}`)
|
|
12
|
+
.join(", ");
|
|
13
|
+
/** A grant joined with its connection (`c`) and the grant (`g`). */
|
|
14
|
+
const GRANT_COLUMNS = `g.id AS grant_id, g.owner_id, g.created_at AS grant_created_at, ${C_COLUMNS}`;
|
|
15
|
+
/**
|
|
16
|
+
* The connection's columns an upsert sets, and the value each takes. They
|
|
17
|
+
* change only for an authorization at least as new as the stored one: Bexio
|
|
18
|
+
* has invalidated the older chain, so a slow connect that authorized earlier
|
|
19
|
+
* must not overwrite a later one. The grant is linked either way.
|
|
20
|
+
*/
|
|
21
|
+
const UPSERT_VALUES = [
|
|
22
|
+
["bexio_company_user_id", "EXCLUDED.bexio_company_user_id"],
|
|
23
|
+
["company_name", "EXCLUDED.company_name"],
|
|
24
|
+
["user_name", "EXCLUDED.user_name"],
|
|
25
|
+
["user_email", "EXCLUDED.user_email"],
|
|
26
|
+
["role", "EXCLUDED.role"],
|
|
27
|
+
["status", "'active'"],
|
|
28
|
+
["access_token", "EXCLUDED.access_token"],
|
|
29
|
+
["refresh_token", "EXCLUDED.refresh_token"],
|
|
30
|
+
["access_expires_at", "EXCLUDED.access_expires_at"],
|
|
31
|
+
["refresh_expires_at", "EXCLUDED.refresh_expires_at"],
|
|
32
|
+
["scope", "EXCLUDED.scope"],
|
|
33
|
+
["refresh_locked_until", "NULL"],
|
|
34
|
+
["refresh_lock_token", "NULL"],
|
|
35
|
+
["authorized_at", "EXCLUDED.authorized_at"],
|
|
36
|
+
["last_refreshed_at", "EXCLUDED.last_refreshed_at"],
|
|
37
|
+
["updated_at", "EXCLUDED.updated_at"],
|
|
38
|
+
];
|
|
39
|
+
const NEWER_AUTHORIZATION_WINS = UPSERT_VALUES.map(([column, value]) => `${column} = CASE WHEN EXCLUDED.authorized_at >= bexio_connections.authorized_at
|
|
40
|
+
THEN ${value} ELSE bexio_connections.${column} END`).join(",\n ");
|
|
41
|
+
/** SQLSTATE for a foreign key violation. */
|
|
42
|
+
const FOREIGN_KEY_VIOLATION = "23503";
|
|
9
43
|
function date(value) {
|
|
10
44
|
if (value === null)
|
|
11
45
|
return null;
|
|
12
46
|
return value instanceof Date ? value : new Date(value);
|
|
13
47
|
}
|
|
14
|
-
function
|
|
48
|
+
function toConnection(row) {
|
|
15
49
|
return {
|
|
16
50
|
id: row.id,
|
|
17
|
-
ownerId: row.owner_id,
|
|
18
51
|
companyId: row.bexio_company_id,
|
|
19
52
|
userId: row.bexio_user_id,
|
|
20
53
|
companyUserId: row.bexio_company_user_id === null
|
|
@@ -31,49 +64,49 @@ function toRecord(row) {
|
|
|
31
64
|
refreshExpiresAt: date(row.refresh_expires_at),
|
|
32
65
|
scope: row.scope,
|
|
33
66
|
refreshLockedUntil: date(row.refresh_locked_until),
|
|
67
|
+
authorizedAt: date(row.authorized_at),
|
|
34
68
|
lastRefreshedAt: date(row.last_refreshed_at),
|
|
35
69
|
createdAt: date(row.created_at),
|
|
36
70
|
updatedAt: date(row.updated_at),
|
|
37
71
|
};
|
|
38
72
|
}
|
|
73
|
+
function toGrant(row) {
|
|
74
|
+
return {
|
|
75
|
+
id: row.grant_id,
|
|
76
|
+
ownerId: row.owner_id,
|
|
77
|
+
createdAt: date(row.grant_created_at),
|
|
78
|
+
connection: toConnection(row),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
39
81
|
/**
|
|
40
|
-
*
|
|
41
|
-
* `npx @soulcode/bexio-oauth sql`). Every operation is a single
|
|
42
|
-
* so it works through transaction-mode poolers and HTTP drivers.
|
|
82
|
+
* Store on the `bexio_connections` and `bexio_grants` tables (create them
|
|
83
|
+
* with `npx @soulcode/bexio-oauth sql`). Every operation is a single
|
|
84
|
+
* statement, so it works through transaction-mode poolers and HTTP drivers.
|
|
43
85
|
*/
|
|
44
86
|
export function postgresStore(sql) {
|
|
45
|
-
const
|
|
46
|
-
const { rows } = await sql.query(text, params);
|
|
47
|
-
return rows[0] ? toRecord(rows[0]) : null;
|
|
48
|
-
};
|
|
49
|
-
const many = async (text, params) => {
|
|
87
|
+
const connection = async (text, params) => {
|
|
50
88
|
const { rows } = await sql.query(text, params);
|
|
51
|
-
return rows
|
|
89
|
+
return rows[0] ? toConnection(rows[0]) : null;
|
|
52
90
|
};
|
|
53
91
|
return {
|
|
54
92
|
async upsertGrant(g) {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
refresh_locked_until = NULL,
|
|
73
|
-
refresh_lock_token = NULL,
|
|
74
|
-
last_refreshed_at = EXCLUDED.last_refreshed_at,
|
|
75
|
-
updated_at = EXCLUDED.updated_at
|
|
76
|
-
RETURNING ${COLUMNS}`, [
|
|
93
|
+
const { rows } = await sql.query(`WITH connection AS (
|
|
94
|
+
INSERT INTO bexio_connections (bexio_company_id, bexio_user_id,
|
|
95
|
+
bexio_company_user_id, company_name, user_name, user_email, role, status,
|
|
96
|
+
access_token, refresh_token, access_expires_at, refresh_expires_at, scope,
|
|
97
|
+
refresh_locked_until, refresh_lock_token, authorized_at, last_refreshed_at,
|
|
98
|
+
created_at, updated_at)
|
|
99
|
+
VALUES ($2, $3, $4, $5, $6, $7, $8, 'active', $9, $10, $11, $12, $13, NULL, NULL, $15, $14, $14, $14)
|
|
100
|
+
ON CONFLICT (bexio_company_id, bexio_user_id) DO UPDATE SET
|
|
101
|
+
${NEWER_AUTHORIZATION_WINS}
|
|
102
|
+
RETURNING ${CONNECTION_COLUMNS}
|
|
103
|
+
), g AS (
|
|
104
|
+
INSERT INTO bexio_grants (owner_id, connection_id, created_at)
|
|
105
|
+
SELECT $1, id, $14 FROM connection
|
|
106
|
+
ON CONFLICT (owner_id, connection_id) DO UPDATE SET owner_id = EXCLUDED.owner_id
|
|
107
|
+
RETURNING id, owner_id, created_at
|
|
108
|
+
)
|
|
109
|
+
SELECT ${GRANT_COLUMNS} FROM g, connection c`, [
|
|
77
110
|
g.ownerId,
|
|
78
111
|
g.companyId,
|
|
79
112
|
g.userId,
|
|
@@ -88,28 +121,64 @@ export function postgresStore(sql) {
|
|
|
88
121
|
g.refreshExpiresAt,
|
|
89
122
|
g.scope,
|
|
90
123
|
g.now,
|
|
124
|
+
g.authorizedAt,
|
|
91
125
|
]);
|
|
92
|
-
if (!
|
|
126
|
+
if (!rows[0]) {
|
|
93
127
|
throw new BexioOAuthError("invalid_config", "SqlClient returned no row for INSERT … RETURNING; check the driver wrapper");
|
|
94
128
|
}
|
|
95
|
-
return
|
|
129
|
+
return toGrant(rows[0]);
|
|
96
130
|
},
|
|
97
131
|
async findGrants({ ownerId, companyId, grantId }) {
|
|
98
132
|
if (grantId !== undefined && !UUID.test(grantId))
|
|
99
133
|
return [];
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
AND ($
|
|
104
|
-
|
|
134
|
+
const { rows } = await sql.query(`SELECT ${GRANT_COLUMNS}
|
|
135
|
+
FROM bexio_grants g JOIN bexio_connections c ON c.id = g.connection_id
|
|
136
|
+
WHERE g.owner_id = $1
|
|
137
|
+
AND ($2::text IS NULL OR c.bexio_company_id = $2)
|
|
138
|
+
AND ($3::uuid IS NULL OR g.id = $3::uuid)
|
|
139
|
+
ORDER BY g.created_at, g.id`, [ownerId, companyId ?? null, grantId ?? null]);
|
|
140
|
+
return rows.map(toGrant);
|
|
105
141
|
},
|
|
106
|
-
async
|
|
142
|
+
async deleteGrant(id) {
|
|
143
|
+
if (!UUID.test(id))
|
|
144
|
+
return;
|
|
145
|
+
await sql.query(`DELETE FROM bexio_grants WHERE id = $1`, [id]);
|
|
146
|
+
},
|
|
147
|
+
async getConnection(id) {
|
|
148
|
+
if (!UUID.test(id))
|
|
149
|
+
return null;
|
|
150
|
+
return connection(`SELECT ${CONNECTION_COLUMNS} FROM bexio_connections WHERE id = $1`, [id]);
|
|
151
|
+
},
|
|
152
|
+
// NOT EXISTS reads the statement's snapshot, so it can miss a grant that
|
|
153
|
+
// a concurrent upsertGrant commits meanwhile. The foreign key catches
|
|
154
|
+
// that case: it checks against committed data and fails the delete.
|
|
155
|
+
async deleteUnusedConnection(id) {
|
|
107
156
|
if (!UUID.test(id))
|
|
108
157
|
return null;
|
|
109
|
-
|
|
158
|
+
try {
|
|
159
|
+
const { rows } = await sql.query(`DELETE FROM bexio_connections c
|
|
160
|
+
WHERE c.id = $1
|
|
161
|
+
AND NOT EXISTS (SELECT 1 FROM bexio_grants g WHERE g.connection_id = c.id)
|
|
162
|
+
RETURNING ${C_COLUMNS}, EXISTS (
|
|
163
|
+
SELECT 1 FROM bexio_connections o
|
|
164
|
+
WHERE o.bexio_user_id = c.bexio_user_id AND o.id <> c.id
|
|
165
|
+
) AS user_has_other_connections`, [id]);
|
|
166
|
+
const row = rows[0];
|
|
167
|
+
return row
|
|
168
|
+
? {
|
|
169
|
+
connection: toConnection(row),
|
|
170
|
+
userHasOtherConnections: row.user_has_other_connections,
|
|
171
|
+
}
|
|
172
|
+
: null;
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
if (error.code === FOREIGN_KEY_VIOLATION)
|
|
176
|
+
return null;
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
110
179
|
},
|
|
111
180
|
async acquireRefreshLock({ id, now, lockUntil, lockToken, accessExpiresBefore, refreshedBefore, }) {
|
|
112
|
-
return
|
|
181
|
+
return connection(`UPDATE bexio_connections SET refresh_locked_until = $3, refresh_lock_token = $6
|
|
113
182
|
WHERE id = $1
|
|
114
183
|
AND status = 'active'
|
|
115
184
|
AND (refresh_locked_until IS NULL OR refresh_locked_until <= $2)
|
|
@@ -117,7 +186,7 @@ export function postgresStore(sql) {
|
|
|
117
186
|
($4::timestamptz IS NOT NULL AND (access_expires_at IS NULL OR access_expires_at <= $4))
|
|
118
187
|
OR ($5::timestamptz IS NOT NULL AND last_refreshed_at < $5)
|
|
119
188
|
)
|
|
120
|
-
RETURNING ${
|
|
189
|
+
RETURNING ${CONNECTION_COLUMNS}`, [
|
|
121
190
|
id,
|
|
122
191
|
now,
|
|
123
192
|
lockUntil,
|
|
@@ -129,7 +198,7 @@ export function postgresStore(sql) {
|
|
|
129
198
|
// Holder writes are fenced by refresh_lock_token: a holder whose lock
|
|
130
199
|
// expired or was cleared by a reconnect matches no row and changes nothing.
|
|
131
200
|
async saveRefreshed(id, lockToken, r) {
|
|
132
|
-
return
|
|
201
|
+
return connection(`UPDATE bexio_connections SET
|
|
133
202
|
access_token = $2,
|
|
134
203
|
refresh_token = COALESCE($3, refresh_token),
|
|
135
204
|
access_expires_at = $4,
|
|
@@ -142,7 +211,7 @@ export function postgresStore(sql) {
|
|
|
142
211
|
last_refreshed_at = $8,
|
|
143
212
|
updated_at = $8
|
|
144
213
|
WHERE id = $1 AND refresh_lock_token = $9
|
|
145
|
-
RETURNING ${
|
|
214
|
+
RETURNING ${CONNECTION_COLUMNS}`, [
|
|
146
215
|
id,
|
|
147
216
|
r.accessToken,
|
|
148
217
|
r.refreshToken,
|
|
@@ -155,22 +224,22 @@ export function postgresStore(sql) {
|
|
|
155
224
|
]);
|
|
156
225
|
},
|
|
157
226
|
async markReauthRequired(id, lockToken, now) {
|
|
158
|
-
|
|
227
|
+
return connection(`UPDATE bexio_connections SET status = 'reauth_required',
|
|
159
228
|
refresh_locked_until = NULL, refresh_lock_token = NULL, updated_at = $2
|
|
160
|
-
WHERE id = $1 AND refresh_lock_token = $3
|
|
229
|
+
WHERE id = $1 AND refresh_lock_token = $3
|
|
230
|
+
RETURNING ${CONNECTION_COLUMNS}`, [id, now, lockToken]);
|
|
161
231
|
},
|
|
162
232
|
async releaseRefreshLock(id, lockToken) {
|
|
163
|
-
await sql.query(`UPDATE
|
|
233
|
+
await sql.query(`UPDATE bexio_connections SET refresh_locked_until = NULL, refresh_lock_token = NULL
|
|
164
234
|
WHERE id = $1 AND refresh_lock_token = $2`, [id, lockToken]);
|
|
165
235
|
},
|
|
166
|
-
async
|
|
167
|
-
await sql.query(`
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
WHERE status = 'active' AND last_refreshed_at < $1
|
|
172
|
-
ORDER BY last_refreshed_at
|
|
236
|
+
async listIdleConnections({ refreshedBefore, limit }) {
|
|
237
|
+
const { rows } = await sql.query(`SELECT ${CONNECTION_COLUMNS} FROM bexio_connections c
|
|
238
|
+
WHERE c.status = 'active' AND c.last_refreshed_at < $1
|
|
239
|
+
AND EXISTS (SELECT 1 FROM bexio_grants g WHERE g.connection_id = c.id)
|
|
240
|
+
ORDER BY c.last_refreshed_at
|
|
173
241
|
LIMIT $2`, [refreshedBefore, limit]);
|
|
242
|
+
return rows.map(toConnection);
|
|
174
243
|
},
|
|
175
244
|
};
|
|
176
245
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres.js","sourceRoot":"","sources":["../../src/store/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAW/C,
|
|
1
|
+
{"version":3,"file":"postgres.js","sourceRoot":"","sources":["../../src/store/postgres.ts"],"names":[],"mappings":"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"}
|
package/dist/store/types.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { Role } from "../permissions.js";
|
|
2
2
|
export type GrantStatus = "active" | "reauth_required";
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* A stored connection: one Bexio identity (company, user) and the tokens of
|
|
5
|
+
* its refresh token chain, shared by every owner that connected it. Token
|
|
6
|
+
* fields hold JWE ciphertext, never plaintext.
|
|
7
|
+
*/
|
|
8
|
+
export interface ConnectionRecord {
|
|
5
9
|
id: string;
|
|
6
|
-
ownerId: string;
|
|
7
10
|
companyId: string;
|
|
8
11
|
userId: string;
|
|
9
12
|
companyUserId: number | null;
|
|
@@ -18,11 +21,26 @@ export interface GrantRecord {
|
|
|
18
21
|
refreshExpiresAt: Date | null;
|
|
19
22
|
scope: string | null;
|
|
20
23
|
refreshLockedUntil: Date | null;
|
|
24
|
+
/** When Bexio issued the authorization the tokens belong to. */
|
|
25
|
+
authorizedAt: Date;
|
|
21
26
|
lastRefreshedAt: Date;
|
|
22
27
|
createdAt: Date;
|
|
23
28
|
updatedAt: Date;
|
|
24
29
|
}
|
|
25
|
-
/**
|
|
30
|
+
/** A stored grant: one owner's access to a connection. */
|
|
31
|
+
export interface GrantRecord {
|
|
32
|
+
id: string;
|
|
33
|
+
ownerId: string;
|
|
34
|
+
createdAt: Date;
|
|
35
|
+
connection: ConnectionRecord;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Upsert of the connection for (company, user) and of the grant for (owner,
|
|
39
|
+
* that connection). The grant is always linked. The connection takes the new
|
|
40
|
+
* tokens (status `active`, lock cleared) unless it already holds a newer
|
|
41
|
+
* authorization: Bexio invalidates older chains, so a slow connect that
|
|
42
|
+
* authorized earlier must not overwrite a later one.
|
|
43
|
+
*/
|
|
26
44
|
export interface GrantUpsert {
|
|
27
45
|
ownerId: string;
|
|
28
46
|
companyId: string;
|
|
@@ -37,9 +55,11 @@ export interface GrantUpsert {
|
|
|
37
55
|
accessExpiresAt: Date | null;
|
|
38
56
|
refreshExpiresAt: Date | null;
|
|
39
57
|
scope: string | null;
|
|
58
|
+
/** When Bexio issued this authorization (the access token's `iat`). */
|
|
59
|
+
authorizedAt: Date;
|
|
40
60
|
now: Date;
|
|
41
61
|
}
|
|
42
|
-
export interface
|
|
62
|
+
export interface ConnectionRefresh {
|
|
43
63
|
accessToken: string;
|
|
44
64
|
/** `null` keeps the stored refresh token (the IdP didn't rotate it). */
|
|
45
65
|
refreshToken: string | null;
|
|
@@ -53,6 +73,7 @@ export interface GrantRefresh {
|
|
|
53
73
|
now: Date;
|
|
54
74
|
}
|
|
55
75
|
export interface LockRequest {
|
|
76
|
+
/** The connection to lock. */
|
|
56
77
|
id: string;
|
|
57
78
|
now: Date;
|
|
58
79
|
lockUntil: Date;
|
|
@@ -64,42 +85,61 @@ export interface LockRequest {
|
|
|
64
85
|
lockToken: string;
|
|
65
86
|
/** Lock only if the access token expires before this instant (lazy refresh)… */
|
|
66
87
|
accessExpiresBefore?: Date;
|
|
67
|
-
/** …or only if the
|
|
88
|
+
/** …or only if the connection was last refreshed before this instant (idle sweep). */
|
|
68
89
|
refreshedBefore?: Date;
|
|
69
90
|
}
|
|
70
91
|
/**
|
|
71
|
-
* Persistence for grants. Every method must be atomic on its
|
|
72
|
-
* library never needs multi-statement transactions.
|
|
92
|
+
* Persistence for connections and grants. Every method must be atomic on its
|
|
93
|
+
* own: the library never needs multi-statement transactions.
|
|
73
94
|
*/
|
|
74
95
|
export interface GrantStore {
|
|
96
|
+
/** See `GrantUpsert`. Returns the grant with its connection. */
|
|
75
97
|
upsertGrant(input: GrantUpsert): Promise<GrantRecord>;
|
|
98
|
+
/** The owner's grants with their connections, oldest first. */
|
|
76
99
|
findGrants(query: {
|
|
77
100
|
ownerId: string;
|
|
78
101
|
companyId?: string;
|
|
79
102
|
grantId?: string;
|
|
80
103
|
}): Promise<GrantRecord[]>;
|
|
81
|
-
|
|
104
|
+
/** Deletes one grant; its connection stays. */
|
|
105
|
+
deleteGrant(id: string): Promise<void>;
|
|
106
|
+
getConnection(id: string): Promise<ConnectionRecord | null>;
|
|
82
107
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
108
|
+
* Deletes the connection if no grant references it any more, and returns
|
|
109
|
+
* the deleted row, plus whether the same Bexio user still has a connection
|
|
110
|
+
* (for another company). Returns `null` (and changes nothing) while a grant
|
|
111
|
+
* still references it, including one added concurrently.
|
|
87
112
|
*/
|
|
88
|
-
|
|
113
|
+
deleteUnusedConnection(id: string): Promise<{
|
|
114
|
+
connection: ConnectionRecord;
|
|
115
|
+
userHasOtherConnections: boolean;
|
|
116
|
+
} | null>;
|
|
117
|
+
/**
|
|
118
|
+
* Takes the refresh lock if the connection is active, not locked (or the
|
|
119
|
+
* lock expired), and still matches the `accessExpiresBefore` /
|
|
120
|
+
* `refreshedBefore` condition. Returns the locked row, or `null` if someone
|
|
121
|
+
* else holds the lock or the connection no longer needs a refresh.
|
|
122
|
+
*/
|
|
123
|
+
acquireRefreshLock(request: LockRequest): Promise<ConnectionRecord | null>;
|
|
89
124
|
/**
|
|
90
125
|
* Stores refreshed tokens, sets `lastRefreshedAt`, and releases the lock.
|
|
91
126
|
* Returns `null` (and changes nothing) unless `lockToken` still holds it.
|
|
92
127
|
*/
|
|
93
|
-
saveRefreshed(id: string, lockToken: string, refresh:
|
|
94
|
-
/**
|
|
95
|
-
|
|
128
|
+
saveRefreshed(id: string, lockToken: string, refresh: ConnectionRefresh): Promise<ConnectionRecord | null>;
|
|
129
|
+
/**
|
|
130
|
+
* Sets `status = reauth_required` and releases the lock. Returns `null`
|
|
131
|
+
* (and changes nothing) unless `lockToken` still holds it.
|
|
132
|
+
*/
|
|
133
|
+
markReauthRequired(id: string, lockToken: string, now: Date): Promise<ConnectionRecord | null>;
|
|
96
134
|
/** Releases the lock, if `lockToken` holds it. */
|
|
97
135
|
releaseRefreshLock(id: string, lockToken: string): Promise<void>;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
136
|
+
/**
|
|
137
|
+
* Active connections that at least one grant references, last refreshed
|
|
138
|
+
* before `refreshedBefore`, oldest first.
|
|
139
|
+
*/
|
|
140
|
+
listIdleConnections(query: {
|
|
101
141
|
refreshedBefore: Date;
|
|
102
142
|
limit: number;
|
|
103
|
-
}): Promise<
|
|
143
|
+
}): Promise<ConnectionRecord[]>;
|
|
104
144
|
}
|
|
105
145
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/store/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,iBAAiB,CAAC;AAEvD
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/store/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface BexioOAuthConfig {
|
|
|
17
17
|
issuer?: string;
|
|
18
18
|
/** Defaults to `https://api.bexio.com`. Override only to point at a mock. */
|
|
19
19
|
apiBaseUrl?: string;
|
|
20
|
-
/** How long one refresh may hold a
|
|
20
|
+
/** How long one refresh may hold a connection's lock. Default `"30s"`. */
|
|
21
21
|
refreshLockTtl?: Duration;
|
|
22
22
|
/** Where `callback` redirects when the flow cookie is gone. Default `"/"`. */
|
|
23
23
|
defaultReturnTo?: string;
|
|
@@ -26,10 +26,16 @@ export interface BexioOAuthConfig {
|
|
|
26
26
|
/** Clock override for tests. */
|
|
27
27
|
clock?: () => Date;
|
|
28
28
|
}
|
|
29
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* A grant as the app sees it: one owner's access to a connection, without
|
|
31
|
+
* token material. Grants of the same Bexio identity share one connection,
|
|
32
|
+
* so they share its tokens, role and status.
|
|
33
|
+
*/
|
|
30
34
|
export interface Grant {
|
|
31
35
|
id: string;
|
|
32
36
|
ownerId: string;
|
|
37
|
+
/** Shared by every grant of the same Bexio company and user. */
|
|
38
|
+
connectionId: string;
|
|
33
39
|
companyId: string;
|
|
34
40
|
companyName: string | null;
|
|
35
41
|
userId: string;
|
|
@@ -41,7 +47,9 @@ export interface Grant {
|
|
|
41
47
|
scope: string | null;
|
|
42
48
|
accessExpiresAt: Date | null;
|
|
43
49
|
lastRefreshedAt: Date;
|
|
50
|
+
/** When this owner connected. */
|
|
44
51
|
createdAt: Date;
|
|
52
|
+
/** When the connection last changed. */
|
|
45
53
|
updatedAt: Date;
|
|
46
54
|
}
|
|
47
55
|
/** Identifies one grant of an owner. */
|
|
@@ -64,15 +72,17 @@ export interface ExternalTokens {
|
|
|
64
72
|
expiresAt?: Date | null;
|
|
65
73
|
scope?: string | null;
|
|
66
74
|
}
|
|
75
|
+
/** Counts connections, not grants: owners of one Bexio identity share one. */
|
|
67
76
|
export interface RefreshIdleResult {
|
|
68
77
|
refreshed: number;
|
|
69
|
-
/** Idle
|
|
78
|
+
/** Idle connections another process was refreshing at the same time. */
|
|
70
79
|
skipped: number;
|
|
71
80
|
reauthRequired: number;
|
|
72
81
|
failed: number;
|
|
73
82
|
/**
|
|
74
|
-
* Idle
|
|
75
|
-
*
|
|
83
|
+
* Idle connections not attempted because `timeBudget` ran out (0 when every
|
|
84
|
+
* idle connection of this run was attempted). The next run picks them up
|
|
85
|
+
* first.
|
|
76
86
|
*/
|
|
77
87
|
remaining: number;
|
|
78
88
|
}
|
|
@@ -133,13 +143,17 @@ export interface BexioOAuth {
|
|
|
133
143
|
listGrants(query: {
|
|
134
144
|
ownerId: string;
|
|
135
145
|
}): Promise<Grant[]>;
|
|
136
|
-
/**
|
|
146
|
+
/**
|
|
147
|
+
* Deletes the owner's grant. When it was the last grant of its connection,
|
|
148
|
+
* also deletes the connection and revokes its refresh token at Bexio (best
|
|
149
|
+
* effort). Other owners of the same Bexio identity keep working.
|
|
150
|
+
*/
|
|
137
151
|
disconnect(selector: GrantSelector): Promise<void>;
|
|
138
152
|
/**
|
|
139
|
-
* Refreshes active
|
|
140
|
-
* their offline sessions. Run it on a schedule. `limit`
|
|
141
|
-
* the
|
|
142
|
-
*
|
|
153
|
+
* Refreshes active connections not refreshed for `idleFor`, so Bexio
|
|
154
|
+
* doesn't end their offline sessions. Run it on a schedule. `limit`
|
|
155
|
+
* (default 100) caps the connections one run looks at; `timeBudget` stops
|
|
156
|
+
* the run before the next connection once that much time has passed.
|
|
143
157
|
*/
|
|
144
158
|
refreshIdle(options: {
|
|
145
159
|
idleFor: Duration;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEhE,MAAM,WAAW,gBAAgB;IAC/B,iCAAiC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,8CAA8C;IAC9C,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;IAClB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcode/bexio-oauth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Bexio OAuth token custody for Node apps: connect Bexio accounts, store tokens encrypted in Postgres, get a valid access token anywhere.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bexio",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"prepack": "npm run build"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"jose": "^6.2.
|
|
50
|
+
"jose": "^6.2.14",
|
|
51
51
|
"openid-client": "^6.8.8"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
-- @soulcode/bexio-oauth 0001: connections and grants.
|
|
2
|
+
-- Token columns hold JWE ciphertext; only your server-side database role
|
|
3
|
+
-- should be able to read these tables.
|
|
4
|
+
|
|
5
|
+
-- One row per Bexio identity (company, user): the tokens of its one refresh
|
|
6
|
+
-- token chain, shared by every owner that connected this identity.
|
|
7
|
+
CREATE TABLE IF NOT EXISTS bexio_connections (
|
|
8
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
9
|
+
bexio_company_id text NOT NULL,
|
|
10
|
+
bexio_user_id text NOT NULL,
|
|
11
|
+
bexio_company_user_id integer,
|
|
12
|
+
company_name text,
|
|
13
|
+
user_name text,
|
|
14
|
+
user_email text,
|
|
15
|
+
role text NOT NULL DEFAULT 'member' CHECK (role IN ('admin', 'member')),
|
|
16
|
+
status text NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'reauth_required')),
|
|
17
|
+
access_token text NOT NULL,
|
|
18
|
+
refresh_token text,
|
|
19
|
+
access_expires_at timestamptz,
|
|
20
|
+
refresh_expires_at timestamptz,
|
|
21
|
+
scope text,
|
|
22
|
+
refresh_locked_until timestamptz,
|
|
23
|
+
refresh_lock_token text,
|
|
24
|
+
authorized_at timestamptz NOT NULL,
|
|
25
|
+
last_refreshed_at timestamptz NOT NULL DEFAULT now(),
|
|
26
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
27
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
28
|
+
CONSTRAINT bexio_connections_company_user_key UNIQUE (bexio_company_id, bexio_user_id)
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
-- refreshIdle(): active connections by last refresh.
|
|
32
|
+
CREATE INDEX IF NOT EXISTS bexio_connections_idle_idx
|
|
33
|
+
ON bexio_connections (last_refreshed_at)
|
|
34
|
+
WHERE status = 'active';
|
|
35
|
+
|
|
36
|
+
-- One row per (owner, connection): an owner's access to a connection. A
|
|
37
|
+
-- connection is deleted only once no grant references it (RESTRICT).
|
|
38
|
+
CREATE TABLE IF NOT EXISTS bexio_grants (
|
|
39
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
40
|
+
owner_id text NOT NULL,
|
|
41
|
+
connection_id uuid NOT NULL REFERENCES bexio_connections (id) ON DELETE RESTRICT,
|
|
42
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
43
|
+
CONSTRAINT bexio_grants_owner_connection_key UNIQUE (owner_id, connection_id)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
-- Disconnect checks whether a connection has grants left.
|
|
47
|
+
CREATE INDEX IF NOT EXISTS bexio_grants_connection_idx
|
|
48
|
+
ON bexio_grants (connection_id);
|