@oxy.so/core 7.17.0 → 7.18.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.
|
@@ -58,6 +58,19 @@ function stringArray(value) {
|
|
|
58
58
|
: null;
|
|
59
59
|
}
|
|
60
60
|
const RELATIONSHIPS = ['self', 'owner', 'member'];
|
|
61
|
+
const APPLICATION_TYPES = [
|
|
62
|
+
'first_party',
|
|
63
|
+
'third_party',
|
|
64
|
+
'internal',
|
|
65
|
+
'system',
|
|
66
|
+
];
|
|
67
|
+
const OFFICIAL_TYPES = ['first_party', 'internal', 'system'];
|
|
68
|
+
/** `deleted` is absent on purpose: Oxy answers a deleted application 404. */
|
|
69
|
+
const APPLICATION_STATUSES = [
|
|
70
|
+
'active',
|
|
71
|
+
'suspended',
|
|
72
|
+
'pending_review',
|
|
73
|
+
];
|
|
61
74
|
/** `GET /accounts/:id` → the caller's effective access, or `null` for a malformed answer. */
|
|
62
75
|
function parseAccountAccess(accountId, answer) {
|
|
63
76
|
const node = record(answer);
|
|
@@ -84,11 +97,16 @@ function parseApplicationAccess(applicationId, answer) {
|
|
|
84
97
|
if (!application || application._id !== applicationId)
|
|
85
98
|
return null;
|
|
86
99
|
const ownerAccountId = application.ownerAccountId;
|
|
100
|
+
const type = application.type;
|
|
101
|
+
const status = application.status;
|
|
87
102
|
const membership = record(application.callerMembership);
|
|
88
103
|
const permissions = stringArray(membership?.permissions);
|
|
89
104
|
if (typeof ownerAccountId !== 'string' ||
|
|
90
105
|
ownerAccountId === '' ||
|
|
91
106
|
typeof application.isOfficial !== 'boolean' ||
|
|
107
|
+
typeof application.isInternal !== 'boolean' ||
|
|
108
|
+
!APPLICATION_TYPES.includes(type) ||
|
|
109
|
+
!APPLICATION_STATUSES.includes(status) ||
|
|
92
110
|
!membership ||
|
|
93
111
|
typeof membership.role !== 'string' ||
|
|
94
112
|
membership.role === '' ||
|
|
@@ -99,7 +117,8 @@ function parseApplicationAccess(applicationId, answer) {
|
|
|
99
117
|
return Object.freeze({
|
|
100
118
|
applicationId,
|
|
101
119
|
ownerAccountId,
|
|
102
|
-
official: application.isOfficial,
|
|
120
|
+
official: application.isOfficial || application.isInternal || OFFICIAL_TYPES.includes(type),
|
|
121
|
+
status,
|
|
103
122
|
role: membership.role,
|
|
104
123
|
...(0, callerAuthority_1.permissionSet)(permissions),
|
|
105
124
|
});
|
|
@@ -53,6 +53,19 @@ function stringArray(value) {
|
|
|
53
53
|
: null;
|
|
54
54
|
}
|
|
55
55
|
const RELATIONSHIPS = ['self', 'owner', 'member'];
|
|
56
|
+
const APPLICATION_TYPES = [
|
|
57
|
+
'first_party',
|
|
58
|
+
'third_party',
|
|
59
|
+
'internal',
|
|
60
|
+
'system',
|
|
61
|
+
];
|
|
62
|
+
const OFFICIAL_TYPES = ['first_party', 'internal', 'system'];
|
|
63
|
+
/** `deleted` is absent on purpose: Oxy answers a deleted application 404. */
|
|
64
|
+
const APPLICATION_STATUSES = [
|
|
65
|
+
'active',
|
|
66
|
+
'suspended',
|
|
67
|
+
'pending_review',
|
|
68
|
+
];
|
|
56
69
|
/** `GET /accounts/:id` → the caller's effective access, or `null` for a malformed answer. */
|
|
57
70
|
export function parseAccountAccess(accountId, answer) {
|
|
58
71
|
const node = record(answer);
|
|
@@ -79,11 +92,16 @@ export function parseApplicationAccess(applicationId, answer) {
|
|
|
79
92
|
if (!application || application._id !== applicationId)
|
|
80
93
|
return null;
|
|
81
94
|
const ownerAccountId = application.ownerAccountId;
|
|
95
|
+
const type = application.type;
|
|
96
|
+
const status = application.status;
|
|
82
97
|
const membership = record(application.callerMembership);
|
|
83
98
|
const permissions = stringArray(membership?.permissions);
|
|
84
99
|
if (typeof ownerAccountId !== 'string' ||
|
|
85
100
|
ownerAccountId === '' ||
|
|
86
101
|
typeof application.isOfficial !== 'boolean' ||
|
|
102
|
+
typeof application.isInternal !== 'boolean' ||
|
|
103
|
+
!APPLICATION_TYPES.includes(type) ||
|
|
104
|
+
!APPLICATION_STATUSES.includes(status) ||
|
|
87
105
|
!membership ||
|
|
88
106
|
typeof membership.role !== 'string' ||
|
|
89
107
|
membership.role === '' ||
|
|
@@ -94,7 +112,8 @@ export function parseApplicationAccess(applicationId, answer) {
|
|
|
94
112
|
return Object.freeze({
|
|
95
113
|
applicationId,
|
|
96
114
|
ownerAccountId,
|
|
97
|
-
official: application.isOfficial,
|
|
115
|
+
official: application.isOfficial || application.isInternal || OFFICIAL_TYPES.includes(type),
|
|
116
|
+
status,
|
|
98
117
|
role: membership.role,
|
|
99
118
|
...permissionSet(permissions),
|
|
100
119
|
});
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
import { type AccountAccessSource, type AccountKind } from '@oxy.so/contracts';
|
|
30
30
|
import type { AccountRelationship } from '../api/accounts';
|
|
31
|
+
import type { ApplicationStatus } from '../api/apps';
|
|
31
32
|
import { type OxyCallerRequest } from './callerAuthority';
|
|
32
33
|
/** What the person behind a request may do in one Oxy account. */
|
|
33
34
|
export interface OxyAccountAccess {
|
|
@@ -48,8 +49,20 @@ export interface OxyApplicationAccess {
|
|
|
48
49
|
readonly applicationId: string;
|
|
49
50
|
/** The account that owns the application; the access derives from the caller's membership there. */
|
|
50
51
|
readonly ownerAccountId: string;
|
|
51
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Whether the application is one of Oxy's own: `isOfficial`, `isInternal`,
|
|
54
|
+
* or typed `first_party` / `internal` / `system` — the rule oxy-api applies
|
|
55
|
+
* to every trust decision (`isTrustedApplication`). All of those fields are
|
|
56
|
+
* staff-controlled; a self-service `third_party` application is never
|
|
57
|
+
* official, whatever its owner sets.
|
|
58
|
+
*/
|
|
52
59
|
readonly official: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* The application's lifecycle status. A `suspended` or `pending_review`
|
|
62
|
+
* application is still answered (its members may read it); whether it may
|
|
63
|
+
* act is the caller's decision.
|
|
64
|
+
*/
|
|
65
|
+
readonly status: ApplicationStatus;
|
|
53
66
|
/** Informational; gate on `permissions` / `has`, never on the role name. */
|
|
54
67
|
readonly role: string;
|
|
55
68
|
/** The application permissions (`app:read`, `app:update`, `webhooks:update`, …), sorted. */
|
package/package.json
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
type AccountKind,
|
|
34
34
|
} from '@oxy.so/contracts';
|
|
35
35
|
import type { AccountRelationship } from '../api/accounts';
|
|
36
|
+
import type { ApplicationStatus, ApplicationType } from '../api/apps';
|
|
36
37
|
import {
|
|
37
38
|
accessFailure,
|
|
38
39
|
CallerAuthorityCache,
|
|
@@ -63,8 +64,20 @@ export interface OxyApplicationAccess {
|
|
|
63
64
|
readonly applicationId: string;
|
|
64
65
|
/** The account that owns the application; the access derives from the caller's membership there. */
|
|
65
66
|
readonly ownerAccountId: string;
|
|
66
|
-
/**
|
|
67
|
+
/**
|
|
68
|
+
* Whether the application is one of Oxy's own: `isOfficial`, `isInternal`,
|
|
69
|
+
* or typed `first_party` / `internal` / `system` — the rule oxy-api applies
|
|
70
|
+
* to every trust decision (`isTrustedApplication`). All of those fields are
|
|
71
|
+
* staff-controlled; a self-service `third_party` application is never
|
|
72
|
+
* official, whatever its owner sets.
|
|
73
|
+
*/
|
|
67
74
|
readonly official: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* The application's lifecycle status. A `suspended` or `pending_review`
|
|
77
|
+
* application is still answered (its members may read it); whether it may
|
|
78
|
+
* act is the caller's decision.
|
|
79
|
+
*/
|
|
80
|
+
readonly status: ApplicationStatus;
|
|
68
81
|
/** Informational; gate on `permissions` / `has`, never on the role name. */
|
|
69
82
|
readonly role: string;
|
|
70
83
|
/** The application permissions (`app:read`, `app:update`, `webhooks:update`, …), sorted. */
|
|
@@ -117,6 +130,19 @@ function stringArray(value: unknown): string[] | null {
|
|
|
117
130
|
}
|
|
118
131
|
|
|
119
132
|
const RELATIONSHIPS: readonly AccountRelationship[] = ['self', 'owner', 'member'];
|
|
133
|
+
const APPLICATION_TYPES: readonly ApplicationType[] = [
|
|
134
|
+
'first_party',
|
|
135
|
+
'third_party',
|
|
136
|
+
'internal',
|
|
137
|
+
'system',
|
|
138
|
+
];
|
|
139
|
+
const OFFICIAL_TYPES: readonly ApplicationType[] = ['first_party', 'internal', 'system'];
|
|
140
|
+
/** `deleted` is absent on purpose: Oxy answers a deleted application 404. */
|
|
141
|
+
const APPLICATION_STATUSES: readonly ApplicationStatus[] = [
|
|
142
|
+
'active',
|
|
143
|
+
'suspended',
|
|
144
|
+
'pending_review',
|
|
145
|
+
];
|
|
120
146
|
|
|
121
147
|
/** `GET /accounts/:id` → the caller's effective access, or `null` for a malformed answer. */
|
|
122
148
|
export function parseAccountAccess(accountId: string, answer: unknown): OxyAccountAccess | null {
|
|
@@ -144,12 +170,17 @@ export function parseApplicationAccess(
|
|
|
144
170
|
const application = record(answer);
|
|
145
171
|
if (!application || application._id !== applicationId) return null;
|
|
146
172
|
const ownerAccountId = application.ownerAccountId;
|
|
173
|
+
const type = application.type as ApplicationType;
|
|
174
|
+
const status = application.status as ApplicationStatus;
|
|
147
175
|
const membership = record(application.callerMembership);
|
|
148
176
|
const permissions = stringArray(membership?.permissions);
|
|
149
177
|
if (
|
|
150
178
|
typeof ownerAccountId !== 'string' ||
|
|
151
179
|
ownerAccountId === '' ||
|
|
152
180
|
typeof application.isOfficial !== 'boolean' ||
|
|
181
|
+
typeof application.isInternal !== 'boolean' ||
|
|
182
|
+
!APPLICATION_TYPES.includes(type) ||
|
|
183
|
+
!APPLICATION_STATUSES.includes(status) ||
|
|
153
184
|
!membership ||
|
|
154
185
|
typeof membership.role !== 'string' ||
|
|
155
186
|
membership.role === '' ||
|
|
@@ -161,7 +192,8 @@ export function parseApplicationAccess(
|
|
|
161
192
|
return Object.freeze({
|
|
162
193
|
applicationId,
|
|
163
194
|
ownerAccountId,
|
|
164
|
-
official: application.isOfficial,
|
|
195
|
+
official: application.isOfficial || application.isInternal || OFFICIAL_TYPES.includes(type),
|
|
196
|
+
status,
|
|
165
197
|
role: membership.role,
|
|
166
198
|
...permissionSet(permissions),
|
|
167
199
|
});
|