@bongos/core 1.21.95 → 1.21.97
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/.bongos-core.json +45 -35
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +6 -0
- package/clients/bongos-client/index.cjs +6 -0
- package/clients/bongos-client/index.d.ts +10 -3
- package/clients/bongos-client/index.mjs +6 -0
- package/docs/api/openapi.json +156 -7
- package/docs/api-reference.md +8 -5
- package/docs/copy-inventory.md +367 -358
- package/docs/copy-registry.json +536 -425
- package/docs/design/project-startup-direction.md +22 -1
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-readings.json +351 -348
- package/modules/provisioning/github-setup.js +98 -7
- package/modules/provisioning/routes/github-setup.js +44 -1
- package/modules/provisioning/routes/provisioning.js +37 -8
- package/modules/public-landing/public/projects.html +441 -36
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/gds/provision-github-setup.js +29 -9
- package/scripts/gds/provision.js +20 -9
- package/src/bongos/github-repos.js +129 -0
- package/src/bongos/routes/github.js +71 -5
- package/src/module-api.js +1 -1
- package/tests/github_one_trip.mjs +4 -4
- package/tests/github_org_account.mjs +379 -0
- package/tests/github_pick_up.mjs +366 -0
- package/tests/projects_hub_pre_uat.mjs +3 -2
- package/tests/provisioning_github_app_routes.mjs +73 -0
- package/tests/wizard_github_feedback.mjs +95 -29
|
@@ -37,8 +37,27 @@ const SETUP_EVENTS = Object.freeze({
|
|
|
37
37
|
installReported: 'github-app-install-reported',
|
|
38
38
|
installed: 'github-app-installed',
|
|
39
39
|
deviceFlow: 'github-app-device-flow',
|
|
40
|
+
// the organisation the founder chose to make the app on (task 1004651); its detail is
|
|
41
|
+
// the org's login. Written by the mint route; absent = the founder's personal account.
|
|
42
|
+
appOrg: 'github-app-org',
|
|
43
|
+
// the founder pressed "Pick up where it stopped" or "Check again" (task 1004652). Like an
|
|
44
|
+
// install report it re-opens the runner's checking window; it claims nothing itself.
|
|
45
|
+
checkRequested: 'github-app-check-requested',
|
|
40
46
|
});
|
|
41
47
|
|
|
48
|
+
const { effectiveManifestStatus } = require('./manifest-truth');
|
|
49
|
+
|
|
50
|
+
// GitHub's manifest conversion code lasts an hour from GitHub's redirect, and the redirect
|
|
51
|
+
// comes after the link was minted, so an attempt younger than this can still be converted
|
|
52
|
+
// with the code it already holds — no new approval, so no second app (task 1004652).
|
|
53
|
+
const CODE_LIFE_MS = 60 * 60 * 1000;
|
|
54
|
+
|
|
55
|
+
// A GitHub login (1-39 chars, letters/digits and single inner hyphens). The org name the
|
|
56
|
+
// mint route takes from the page passes this before it reaches a GitHub URL, and the
|
|
57
|
+
// recorded one passes it again before it reaches a link. PURE.
|
|
58
|
+
const GITHUB_LOGIN_RE = /^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$/;
|
|
59
|
+
function validGithubLogin(s) { return typeof s === 'string' && GITHUB_LOGIN_RE.test(s); }
|
|
60
|
+
|
|
42
61
|
// How long after an attempt settles the runner keeps checking for the install and the
|
|
43
62
|
// device-flow tick. Past it the checklist stops promising a self-tick for device flow
|
|
44
63
|
// (the step says "turn it on later"); a fresh install report re-opens the window.
|
|
@@ -65,16 +84,24 @@ function targetRepo(inst) {
|
|
|
65
84
|
// The verified facts for one instance since `since` (the attempt's created_at): when each
|
|
66
85
|
// setup event was first seen, or null. One indexed read (idx_provisioning_events_instance).
|
|
67
86
|
async function githubSetupFacts(db, instanceId, since) {
|
|
87
|
+
// `detail` is the newest row's, for the one event that carries a value (appOrg)
|
|
68
88
|
const { rows } = await db.query(
|
|
69
|
-
`SELECT event, min(created_at) AS at
|
|
89
|
+
`SELECT event, min(created_at) AS at, (array_agg(detail ORDER BY created_at DESC))[1] AS detail
|
|
90
|
+
FROM provisioning_events
|
|
70
91
|
WHERE instance_id = $1 AND event = ANY($2) AND created_at >= $3
|
|
71
92
|
GROUP BY event`,
|
|
72
93
|
[instanceId, Object.values(SETUP_EVENTS), since || new Date(0).toISOString()]);
|
|
94
|
+
const find = (name) => rows.find((x) => x.event === name);
|
|
73
95
|
const at = (name) => {
|
|
74
|
-
const r =
|
|
96
|
+
const r = find(name);
|
|
75
97
|
return r && r.at ? new Date(r.at).toISOString() : null;
|
|
76
98
|
};
|
|
77
|
-
|
|
99
|
+
const org = find(SETUP_EVENTS.appOrg);
|
|
100
|
+
return {
|
|
101
|
+
installReportedAt: at(SETUP_EVENTS.installReported), installedAt: at(SETUP_EVENTS.installed), deviceFlowAt: at(SETUP_EVENTS.deviceFlow),
|
|
102
|
+
checkRequestedAt: at(SETUP_EVENTS.checkRequested),
|
|
103
|
+
appOrg: org && validGithubLogin(org.detail) ? org.detail : null,
|
|
104
|
+
};
|
|
78
105
|
}
|
|
79
106
|
|
|
80
107
|
// Each checklist step's state, from the latest attempt's EFFECTIVE status (manifest-truth's
|
|
@@ -101,10 +128,65 @@ function githubChecklist(status, appSlug, facts = {}) {
|
|
|
101
128
|
// device-flow step can still tick itself, so the page keeps polling. PURE.
|
|
102
129
|
function stillVerifying(row, facts = {}, now = Date.now()) {
|
|
103
130
|
const settled = row && (row.resolved_at || row.updated_at);
|
|
104
|
-
const
|
|
131
|
+
const t = (iso) => (iso ? new Date(iso).getTime() : 0);
|
|
132
|
+
const last = Math.max(t(settled), t(facts.installReportedAt), t(facts.checkRequestedAt));
|
|
105
133
|
return last > 0 && now - last < VERIFY_WINDOW_MS;
|
|
106
134
|
}
|
|
107
135
|
|
|
136
|
+
// What "Pick up where it stopped" does for this attempt (task 1004652) — a closed
|
|
137
|
+
// vocabulary, and never "make another app" once GitHub has made one:
|
|
138
|
+
// 'reopen' — the link is still waiting on GitHub: post the SAME attempt again.
|
|
139
|
+
// 'fresh' — nothing was made on GitHub (the link lapsed, or the runner never took
|
|
140
|
+
// it up): a new link is safe.
|
|
141
|
+
// 'convert' — GitHub approved and the code it gave is still young: convert it again.
|
|
142
|
+
// 'check' — the app exists with its keys placed, a step is unverified and the runner
|
|
143
|
+
// has stopped checking: ask it to look again (sign-in, install, device flow).
|
|
144
|
+
// 'operator' — GitHub approved but the app's keys never reached us and its code is
|
|
145
|
+
// spent or too old: only the platform team can finish it.
|
|
146
|
+
// null — nothing to pick up: in flight, still being checked, or all done.
|
|
147
|
+
// PURE.
|
|
148
|
+
function pickUpFor(row, status, checklist, verifying, now = Date.now()) {
|
|
149
|
+
if (!row) return null;
|
|
150
|
+
if (status === 'pending') return 'reopen';
|
|
151
|
+
if (status === 'expired') return 'fresh';
|
|
152
|
+
if (row.app_slug || row.app_id) {
|
|
153
|
+
if (checklist && checklist.connected && checklist.device_flow === 'done') return null;
|
|
154
|
+
return verifying ? null : 'check';
|
|
155
|
+
}
|
|
156
|
+
if (status !== 'error') return null;
|
|
157
|
+
if (!(Number(row.attempts) > 0)) return 'fresh';
|
|
158
|
+
const age = now - new Date(row.created_at || 0).getTime();
|
|
159
|
+
return row.github_code && age < CODE_LIFE_MS ? 'convert' : 'operator';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// May the mint route make a new link for this instance, given its latest attempt (task
|
|
163
|
+
// 1004652)? null = yes. { reuse } = the latest link is still waiting on GitHub, so hand
|
|
164
|
+
// back the SAME state (a second click, a reopened tab, another device) instead of a second
|
|
165
|
+
// attempt. { refuse, message } = GitHub has approved an app for this project already, so a
|
|
166
|
+
// new link would make a second one. PURE.
|
|
167
|
+
function mintGuard(row, now = Date.now()) {
|
|
168
|
+
if (!row) return null;
|
|
169
|
+
const status = effectiveManifestStatus(row, now);
|
|
170
|
+
if (status === 'pending') return { reuse: row.state, expiresAt: new Date(row.expires_at).toISOString() };
|
|
171
|
+
if (status === 'expired' || (status === 'error' && !(Number(row.attempts) > 0) && !row.app_id)) return null;
|
|
172
|
+
if (status === 'code_received' || status === 'converting') {
|
|
173
|
+
return { refuse: 'github_in_progress', message: 'GitHub already approved this project’s app, and the platform is finishing it. The checklist ticks itself.' };
|
|
174
|
+
}
|
|
175
|
+
return { refuse: 'app_exists', message: 'GitHub already made this project’s app, so we won’t make a second one. Pick up where it stopped instead.' };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Put an approved attempt whose conversion stopped back in the runner's queue, with the
|
|
179
|
+
// code it still holds (pickUpFor 'convert'). The attempt count restarts so the runner
|
|
180
|
+
// tries it again. Only an attempt still in that shape moves; false otherwise.
|
|
181
|
+
async function requeueConversion(db, id) {
|
|
182
|
+
const { rows } = await db.query(
|
|
183
|
+
`UPDATE provisioning_oauth_manifests
|
|
184
|
+
SET status = 'code_received', attempts = 0, last_error = NULL, resolved_at = NULL, updated_at = now()
|
|
185
|
+
WHERE id = $1 AND status = 'error' AND app_id IS NULL AND github_code IS NOT NULL
|
|
186
|
+
RETURNING id`, [id]);
|
|
187
|
+
return rows.length > 0;
|
|
188
|
+
}
|
|
189
|
+
|
|
108
190
|
// What the public status poll and the owner's instance read add for this leg: the checklist,
|
|
109
191
|
// the install link (until the install is verified), the repo it belongs on, and whether the
|
|
110
192
|
// runner is still checking. `status` is manifest-truth's EFFECTIVE status. Never the raw row.
|
|
@@ -113,17 +195,26 @@ function stillVerifying(row, facts = {}, now = Date.now()) {
|
|
|
113
195
|
// lands on the setup page, which sends the founder back to their projects.
|
|
114
196
|
// A failed facts read is "nothing verified yet" — the honest 'now' states, never a 500.
|
|
115
197
|
async function githubSetupRead(db, inst, row, status, { now = Date.now(), withState = true } = {}) {
|
|
116
|
-
if (!row) return { checklist: githubChecklist(null, null), install_url: null, repo: targetRepo(inst), verifying: false };
|
|
198
|
+
if (!row) return { checklist: githubChecklist(null, null), install_url: null, repo: targetRepo(inst), verifying: false, app_org: null, pick_up: null };
|
|
117
199
|
let facts = {};
|
|
118
200
|
// through module.exports so a route test can stub the one DB read on the cached module
|
|
119
201
|
try { facts = await module.exports.githubSetupFacts(db, row.instance_id, row.created_at); } catch (_) { facts = {}; }
|
|
120
202
|
const checklist = githubChecklist(status, row.app_slug, facts);
|
|
203
|
+
const verifying = stillVerifying(row, facts, now);
|
|
121
204
|
return {
|
|
122
205
|
checklist,
|
|
123
206
|
install_url: checklist.install === 'done' ? null : installUrlFor({ appSlug: row.app_slug, state: withState ? row.state : null }),
|
|
124
207
|
repo: targetRepo(inst),
|
|
125
|
-
verifying
|
|
208
|
+
verifying,
|
|
209
|
+
// what "Pick up where it stopped" will do (task 1004652); never a secret, never the code
|
|
210
|
+
pick_up: pickUpFor(row, status, checklist, verifying, now),
|
|
211
|
+
// the organisation this attempt makes the app on, or null for the founder's account
|
|
212
|
+
// (task 1004651): the page links the app's settings under the right owner
|
|
213
|
+
app_org: validGithubLogin(facts.appOrg) ? facts.appOrg : null,
|
|
126
214
|
};
|
|
127
215
|
}
|
|
128
216
|
|
|
129
|
-
module.exports = {
|
|
217
|
+
module.exports = {
|
|
218
|
+
SETUP_EVENTS, VERIFY_WINDOW_MS, installUrlFor, targetRepo, githubSetupFacts, githubChecklist, stillVerifying, githubSetupRead,
|
|
219
|
+
validGithubLogin, pickUpFor, mintGuard, requeueConversion,
|
|
220
|
+
};
|
|
@@ -91,7 +91,8 @@ module.exports = function buildGithubSetupRouter() {
|
|
|
91
91
|
status,
|
|
92
92
|
failure,
|
|
93
93
|
ready: status === 'done',
|
|
94
|
-
|
|
94
|
+
// a stopped sign-in the runner is still re-checking is not the end (task 1004652)
|
|
95
|
+
terminal: (failed && !setup.verifying) || (setup.checklist.connected && (setup.checklist.device_flow === 'done' || !setup.verifying)),
|
|
95
96
|
domain: inst && inst.domain ? inst.domain : null,
|
|
96
97
|
instance_status: inst ? inst.status : null,
|
|
97
98
|
// The App's public slug (task 1003049) — its GitHub page is public by design.
|
|
@@ -100,6 +101,8 @@ module.exports = function buildGithubSetupRouter() {
|
|
|
100
101
|
install_url: setup.install_url,
|
|
101
102
|
repo: setup.repo,
|
|
102
103
|
verifying: setup.verifying,
|
|
104
|
+
app_org: setup.app_org,
|
|
105
|
+
pick_up: setup.pick_up,
|
|
103
106
|
});
|
|
104
107
|
} catch (err) {
|
|
105
108
|
log.error('[provisioning] GET /provisioning/github-app/status', err);
|
|
@@ -183,6 +186,46 @@ module.exports = function buildGithubSetupRouter() {
|
|
|
183
186
|
}
|
|
184
187
|
});
|
|
185
188
|
|
|
189
|
+
// POST /provisioning/instances/:id/github-app/pick-up — "Pick up where it stopped" and
|
|
190
|
+
// "Check again" (task 1004652). It resumes the latest attempt from the step it stopped at,
|
|
191
|
+
// with the app GitHub already made, and never mints a new link: 'convert' puts an approved
|
|
192
|
+
// attempt whose code is still young back in the runner's queue; 'check' records a check
|
|
193
|
+
// request, which re-opens the runner's checking window for sign-in, the install and
|
|
194
|
+
// device flow. Every other pick-up is the page's own ('reopen' and 'fresh' go through the
|
|
195
|
+
// mint route, whose guard reuses or refuses; 'operator' files a blocker), so they are a
|
|
196
|
+
// 409 naming the action. Answers with the fresh read. rank: any authenticated builder
|
|
197
|
+
// (requireBuilder), the project's OWNER only — no Archon override, as with the mint route;
|
|
198
|
+
// anyone else gets the same 404 as a missing project.
|
|
199
|
+
router.post('/provisioning/instances/:id/github-app/pick-up', api.requireBuilder, returnRateLimit, async (req, res) => {
|
|
200
|
+
const id = /^\d{1,12}$/.test(String(req.params.id)) ? Number(req.params.id) : null;
|
|
201
|
+
if (!id) return res.fail('bad_id', 400);
|
|
202
|
+
try {
|
|
203
|
+
const inst = await provisioning.getInstanceById(pool, id);
|
|
204
|
+
if (!ownsLanding(inst, req.builder && req.builder.id)) return res.fail('instance_not_found', 404);
|
|
205
|
+
const row = await provisioning.getLatestOAuthManifestForInstance(pool, id);
|
|
206
|
+
const read = async (r) => githubSetup.githubSetupRead(pool, inst, r, r ? provisioning.publicGithubApp(r).status : null, { withState: false });
|
|
207
|
+
const before = await read(row);
|
|
208
|
+
const action = before.pick_up;
|
|
209
|
+
if (action === 'convert') {
|
|
210
|
+
if (!(await githubSetup.requeueConversion(pool, row.id))) return res.fail('nothing_to_pick_up', { status: 409, message: 'This step has already moved on.' });
|
|
211
|
+
} else if (action === 'check') {
|
|
212
|
+
await provisioning.recordEvent(pool, {
|
|
213
|
+
instanceId: id, ownerBuilderId: req.builder.id, event: githubSetup.SETUP_EVENTS.checkRequested,
|
|
214
|
+
detail: 'the founder asked to pick up the GitHub setup where it stopped', actor: 'api:self',
|
|
215
|
+
});
|
|
216
|
+
} else {
|
|
217
|
+
return res.fail('nothing_to_pick_up', { status: 409, message: 'Nothing to pick up here.', action: action || null });
|
|
218
|
+
}
|
|
219
|
+
const after = await provisioning.getLatestOAuthManifestForInstance(pool, id);
|
|
220
|
+
const pub = provisioning.publicGithubApp(after);
|
|
221
|
+
const setup = await read(after);
|
|
222
|
+
return res.json({ ok: true, action, status: pub.status, failure: pub.failure, app_slug: after.app_slug || null, ...setup });
|
|
223
|
+
} catch (err) {
|
|
224
|
+
log.error('[provisioning] POST /provisioning/instances/:id/github-app/pick-up', err);
|
|
225
|
+
return res.status(500).json({ ok: false, error: 'pick_up_failed' });
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
186
229
|
// GET /provisioning/github-app/resume[?id=<n>] — the project page's "which project am I
|
|
187
230
|
// connecting?" read when its tab remembers nothing (task 1004650): a hand-back on another
|
|
188
231
|
// device, a fresh tab, a cleared browser. With an id: that project, if the caller owns it.
|
|
@@ -1230,11 +1230,22 @@ module.exports = function provisioningRoutes() {
|
|
|
1230
1230
|
// GitHub, the owner approves, and GitHub redirects back to the public callback below.
|
|
1231
1231
|
// The web tier only RECORDS the attempt (state token) — the control-plane runner does
|
|
1232
1232
|
// the token exchange + cred placement (trust boundary intact, ADR 0111 §2).
|
|
1233
|
-
// Body: { account? ('personal'|'org') }.
|
|
1233
|
+
// Body: { account? ('personal'|'org'), org? (a GitHub organisation login) }.
|
|
1234
|
+
// A personal or an organisation account (task 1004651): account 'org' makes the app on
|
|
1235
|
+
// the organisation the founder chose on the page (`org`), else on the repo's owner. The
|
|
1236
|
+
// org name is checked against GitHub's login rules here, never trusted from the page;
|
|
1237
|
+
// whether the founder may make an app there is GitHub's to enforce on its own page
|
|
1238
|
+
// (this module holds no GitHub token to ask with, ADR 0111 §2). Anything other than
|
|
1239
|
+
// account 'org' is the founder's personal account, exactly as before.
|
|
1234
1240
|
router.post('/provisioning/instances/:id/github-app', auth.requireBuilder, async (req, res) => {
|
|
1235
1241
|
const id = parseId(req, res); // (req, res) — parseId sends 400 bad_id + returns null on failure
|
|
1236
1242
|
if (!id) return;
|
|
1237
|
-
if (validateOrRespond(req, res, { account: { type: 'string', maxLength: 12 } })) return;
|
|
1243
|
+
if (validateOrRespond(req, res, { account: { type: 'string', maxLength: 12 }, org: { type: 'string', maxLength: 39 } })) return;
|
|
1244
|
+
const wantsOrg = !!(req.body && req.body.account === 'org');
|
|
1245
|
+
const typedOrg = wantsOrg && typeof req.body.org === 'string' ? req.body.org.trim() : '';
|
|
1246
|
+
if (typedOrg && !githubSetup.validGithubLogin(typedOrg)) {
|
|
1247
|
+
return res.fail('bad_org', { status: 400, message: 'That isn’t a GitHub organisation name: letters, numbers and single dashes, up to 39.' });
|
|
1248
|
+
}
|
|
1238
1249
|
try {
|
|
1239
1250
|
const inst = await provisioning.getInstanceById(pool, id);
|
|
1240
1251
|
if (!inst || String(inst.owner_builder_id) !== String(req.builder.id)) {
|
|
@@ -1249,20 +1260,38 @@ module.exports = function provisioningRoutes() {
|
|
|
1249
1260
|
if (!origin) {
|
|
1250
1261
|
return res.fail('mothership_origin_unset', { status: 500, message: 'This control plane has no publicOrigin configured — cannot build the GitHub redirect URL.' });
|
|
1251
1262
|
}
|
|
1252
|
-
|
|
1253
|
-
const
|
|
1254
|
-
|
|
1263
|
+
// the org candidate: the one the founder chose, else the repo's owner (a login only)
|
|
1264
|
+
const repoOwner = ownerFromTargetRef(inst.target_ref);
|
|
1265
|
+
const orgLogin = typedOrg || (githubSetup.validGithubLogin(repoOwner) ? repoOwner : null);
|
|
1266
|
+
// an explicit organisation choice never falls back to the personal account unseen
|
|
1267
|
+
if (wantsOrg && !orgLogin) {
|
|
1268
|
+
return res.fail('org_required', { status: 400, message: 'Name the GitHub organisation to make the app on.' });
|
|
1269
|
+
}
|
|
1270
|
+
const account = wantsOrg ? 'org' : 'personal';
|
|
1271
|
+
// never a second app (task 1004652): a link still waiting on GitHub is handed back,
|
|
1272
|
+
// and once GitHub has approved one for this project no new link is made
|
|
1273
|
+
const guard = githubSetup.mintGuard(await provisioning.getLatestOAuthManifestForInstance(pool, id));
|
|
1274
|
+
if (guard && guard.refuse) return res.fail(guard.refuse, { status: 409, message: guard.message });
|
|
1275
|
+
const state = guard ? guard.reuse : crypto.randomBytes(32).toString('hex');
|
|
1276
|
+
const expiresAt = guard ? guard.expiresAt : new Date(Date.now() + MANIFEST_STATE_TTL_MS).toISOString();
|
|
1277
|
+
if (!guard) await provisioning.createOAuthManifest(pool, { instanceId: id, state, requestedBy: req.builder.id, expiresAt });
|
|
1255
1278
|
await provisioning.recordEvent(pool, {
|
|
1256
1279
|
instanceId: id, ownerBuilderId: req.builder.id, event: 'oauth-manifest-start',
|
|
1257
|
-
detail: 'GitHub App Manifest link minted', actor: 'api:self',
|
|
1280
|
+
detail: guard ? 'GitHub App Manifest link reopened (same attempt)' : 'GitHub App Manifest link minted', actor: 'api:self',
|
|
1258
1281
|
}).catch(() => {});
|
|
1282
|
+
// which account owns this attempt's app, so the page can link its settings there
|
|
1283
|
+
// from any tab or device (github-setup.js reads it back as app_org)
|
|
1284
|
+
if (account === 'org') {
|
|
1285
|
+
await provisioning.recordEvent(pool, {
|
|
1286
|
+
instanceId: id, ownerBuilderId: req.builder.id, event: githubSetup.SETUP_EVENTS.appOrg,
|
|
1287
|
+
detail: orgLogin, actor: 'api:self',
|
|
1288
|
+
}).catch(() => {});
|
|
1289
|
+
}
|
|
1259
1290
|
|
|
1260
1291
|
const manifest = provisioning.buildGithubAppManifest({
|
|
1261
1292
|
instanceName: inst.slug, slug: inst.slug, instanceDomain: inst.domain,
|
|
1262
1293
|
mothershipOrigin: origin, brand: mothershipBrand(),
|
|
1263
1294
|
});
|
|
1264
|
-
const orgLogin = ownerFromTargetRef(inst.target_ref);
|
|
1265
|
-
const account = req.body && req.body.account === 'org' && orgLogin ? 'org' : 'personal';
|
|
1266
1295
|
res.json({
|
|
1267
1296
|
state,
|
|
1268
1297
|
account,
|