@jeffusion/bungee-core 5.11.0 → 5.12.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/dist/main.js +150 -51
- package/dist/plugins/chatgpt-oauth/control.js +338 -47
- package/dist/plugins/chatgpt-oauth/control.js.map +8 -7
- package/dist/plugins/chatgpt-oauth/index.js +181 -13
- package/dist/plugins/chatgpt-oauth/index.js.map +4 -4
- package/dist/plugins/chatgpt-oauth/manifest.json +98 -7
- package/dist/plugins/chatgpt-oauth/ui/AccountsPage.svelte +25 -15
- package/dist/plugins/chatgpt-oauth/ui/ChatgptQuotaWidget.svelte +7 -4
- package/dist/plugins/chatgpt-oauth/ui/account-model.js +16 -7
- package/package.json +2 -2
|
@@ -506,7 +506,7 @@ var businessRpc = defineRpcService({ id: "chatgpt-oauth.credentials.v1", version
|
|
|
506
506
|
} });
|
|
507
507
|
|
|
508
508
|
// plugins/chatgpt-oauth/server/control.ts
|
|
509
|
-
import { randomUUID as
|
|
509
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
510
510
|
|
|
511
511
|
// plugins/chatgpt-oauth/server/oauth.ts
|
|
512
512
|
import { createHash, randomBytes } from "crypto";
|
|
@@ -1025,8 +1025,181 @@ function extractCodexIdentity(idToken) {
|
|
|
1025
1025
|
};
|
|
1026
1026
|
}
|
|
1027
1027
|
|
|
1028
|
+
// plugins/chatgpt-oauth/server/siwc.ts
|
|
1029
|
+
import { createPublicKey, randomBytes as randomBytes2, randomUUID, verify } from "crypto";
|
|
1030
|
+
var SIWC_AUTH_URL = "https://auth.openai.com/api/accounts/authorize";
|
|
1031
|
+
var SIWC_TOKEN_URL = "https://auth.openai.com/api/accounts/oauth/token";
|
|
1032
|
+
var SIWC_JWKS_URL = "https://auth.openai.com/.well-known/jwks.json";
|
|
1033
|
+
var SIWC_ISSUER = "https://auth.openai.com";
|
|
1034
|
+
var SIWC_REDIRECT_URI = "http://127.0.0.1:1455/auth/callback";
|
|
1035
|
+
var SIWC_RESOURCE = "https://api.openai.com/v1";
|
|
1036
|
+
var SIWC_SCOPES = ["openid", "profile", "email", "offline_access", "resource.invoke", "chatgpt.tokens.use.direct"];
|
|
1037
|
+
var SIWC_REGISTRATION_KEY = "siwc.registration.v1";
|
|
1038
|
+
var CLIENT_ID = /^oaiapp_[A-Za-z0-9_-]{1,200}$/;
|
|
1039
|
+
var HOST_ID = /^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
1040
|
+
function invalid(kind = "invalid_response") {
|
|
1041
|
+
throw new CodexOAuthError(kind, `SIWC ${kind === "invalid_callback" ? "callback" : "response"} is invalid`, { retryable: false });
|
|
1042
|
+
}
|
|
1043
|
+
async function getSiwcHostId(store, canWrite = () => true) {
|
|
1044
|
+
for (let attempt = 0;attempt < 12; attempt++) {
|
|
1045
|
+
if (!canWrite())
|
|
1046
|
+
throw new CodexOAuthError("cancelled", "SIWC operation was cancelled", { retryable: false });
|
|
1047
|
+
const current = await store.get(SIWC_REGISTRATION_KEY);
|
|
1048
|
+
if (current) {
|
|
1049
|
+
try {
|
|
1050
|
+
const record = JSON.parse(current.value);
|
|
1051
|
+
if (record?.schema === 1 && typeof record.hostId === "string" && HOST_ID.test(record.hostId))
|
|
1052
|
+
return record.hostId;
|
|
1053
|
+
} catch {}
|
|
1054
|
+
return invalid();
|
|
1055
|
+
}
|
|
1056
|
+
const hostId = `urn:uuid:${randomUUID()}`;
|
|
1057
|
+
if (!canWrite())
|
|
1058
|
+
throw new CodexOAuthError("cancelled", "SIWC operation was cancelled", { retryable: false });
|
|
1059
|
+
try {
|
|
1060
|
+
await store.compareAndSet(SIWC_REGISTRATION_KEY, null, JSON.stringify({ schema: 1, hostId }));
|
|
1061
|
+
return hostId;
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
if (error.code !== "version_conflict")
|
|
1064
|
+
throw error;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
return invalid();
|
|
1068
|
+
}
|
|
1069
|
+
function validSiwcMetadata(value) {
|
|
1070
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1071
|
+
return false;
|
|
1072
|
+
const item = value;
|
|
1073
|
+
return Object.keys(item).every((key) => ["clientId", "subject", "scopes"].includes(key)) && typeof item.clientId === "string" && CLIENT_ID.test(item.clientId) && typeof item.subject === "string" && item.subject.length > 0 && item.subject.length <= 512 && item.subject.trim() === item.subject && !/[\u0000-\u001f\u007f]/.test(item.subject) && Array.isArray(item.scopes) && item.scopes.length <= 128 && item.scopes.every((scope) => typeof scope === "string" && /^[\x21\x23-\x5b\x5d-\x7e]{1,128}$/.test(scope)) && item.scopes.includes("resource.invoke") && item.scopes.includes("chatgpt.tokens.use.direct");
|
|
1074
|
+
}
|
|
1075
|
+
function createSiwcNonce(random = randomBytes2) {
|
|
1076
|
+
return Buffer.from(random(32)).toString("base64url");
|
|
1077
|
+
}
|
|
1078
|
+
function buildSiwcAuthorizationUrl(pkce, nonce, hostId, clientId) {
|
|
1079
|
+
if (!HOST_ID.test(hostId) || clientId !== undefined && !CLIENT_ID.test(clientId) || !nonce || !pkce.state || !pkce.codeChallenge)
|
|
1080
|
+
return invalid();
|
|
1081
|
+
const url = new URL(SIWC_AUTH_URL);
|
|
1082
|
+
url.search = new URLSearchParams({
|
|
1083
|
+
client_id: clientId ?? "dynamic_agent_client",
|
|
1084
|
+
agent_name_hint: "Bungee",
|
|
1085
|
+
ext_agent_host_id: hostId,
|
|
1086
|
+
response_type: "code",
|
|
1087
|
+
redirect_uri: SIWC_REDIRECT_URI,
|
|
1088
|
+
resource: SIWC_RESOURCE,
|
|
1089
|
+
scope: SIWC_SCOPES.join(" "),
|
|
1090
|
+
state: pkce.state,
|
|
1091
|
+
nonce,
|
|
1092
|
+
code_challenge: pkce.codeChallenge,
|
|
1093
|
+
code_challenge_method: "S256"
|
|
1094
|
+
}).toString();
|
|
1095
|
+
return url.toString();
|
|
1096
|
+
}
|
|
1097
|
+
function parseSiwcCallbackUrl(raw, options) {
|
|
1098
|
+
let url;
|
|
1099
|
+
try {
|
|
1100
|
+
url = new URL(raw);
|
|
1101
|
+
} catch {
|
|
1102
|
+
return invalid("invalid_callback");
|
|
1103
|
+
}
|
|
1104
|
+
const expected = new URL(SIWC_REDIRECT_URI);
|
|
1105
|
+
const now = options.now ?? Date.now();
|
|
1106
|
+
if (raw.length > 4096 || raw.trim() !== raw || raw.split("?")[0] !== SIWC_REDIRECT_URI || url.origin !== expected.origin || url.pathname !== expected.pathname || url.username || url.password || url.hash || !Number.isFinite(now) || !Number.isFinite(options.issuedAt) || options.issuedAt < 0 || now < options.issuedAt || now - options.issuedAt > CODEX_CALLBACK_TTL_MS || [...url.searchParams.keys()].some((key) => url.searchParams.getAll(key).length !== 1) || url.searchParams.has("userinfo") || !options.expectedState || url.searchParams.get("state") !== options.expectedState || url.searchParams.has("error") || url.searchParams.has("error_description"))
|
|
1107
|
+
return invalid("invalid_callback");
|
|
1108
|
+
const code = url.searchParams.get("code");
|
|
1109
|
+
const clientId = url.searchParams.get("client_id");
|
|
1110
|
+
if (!code || code.trim() !== code || /[\u0000-\u001f\u007f]/.test(code) || !clientId || !CLIENT_ID.test(clientId) || options.expectedClientId !== undefined && clientId !== options.expectedClientId)
|
|
1111
|
+
return invalid("invalid_callback");
|
|
1112
|
+
return { code, clientId };
|
|
1113
|
+
}
|
|
1114
|
+
function jwtObject(part) {
|
|
1115
|
+
try {
|
|
1116
|
+
if (!/^[A-Za-z0-9_-]+$/.test(part))
|
|
1117
|
+
return invalid();
|
|
1118
|
+
const value = JSON.parse(Buffer.from(part, "base64url").toString("utf8"));
|
|
1119
|
+
if (value && typeof value === "object" && !Array.isArray(value))
|
|
1120
|
+
return value;
|
|
1121
|
+
} catch {}
|
|
1122
|
+
return invalid();
|
|
1123
|
+
}
|
|
1124
|
+
async function verifySiwcIdToken(idToken, expected, options = {}) {
|
|
1125
|
+
if (!CLIENT_ID.test(expected.clientId) || idToken.length > 32768)
|
|
1126
|
+
return invalid();
|
|
1127
|
+
const parts = idToken.split(".");
|
|
1128
|
+
if (parts.length !== 3 || !/^[A-Za-z0-9_-]+$/.test(parts[2]))
|
|
1129
|
+
return invalid();
|
|
1130
|
+
const header = jwtObject(parts[0]);
|
|
1131
|
+
const payload = jwtObject(parts[1]);
|
|
1132
|
+
if (header.alg !== "RS256" || typeof header.kid !== "string" || !header.kid || header.crit !== undefined)
|
|
1133
|
+
return invalid();
|
|
1134
|
+
const jwks = await requestJson(SIWC_JWKS_URL, { method: "GET", headers: { accept: "application/json" } }, options);
|
|
1135
|
+
if (!Array.isArray(jwks.keys) || jwks.keys.length > 128)
|
|
1136
|
+
return invalid();
|
|
1137
|
+
const keys = jwks.keys.filter((key) => key && typeof key === "object" && key.kid === header.kid && key.kty === "RSA" && (key.alg === undefined || key.alg === "RS256") && (key.use === undefined || key.use === "sig") && (key.key_ops === undefined || Array.isArray(key.key_ops) && key.key_ops.includes("verify")) && key.d === undefined);
|
|
1138
|
+
if (keys.length !== 1)
|
|
1139
|
+
return invalid();
|
|
1140
|
+
try {
|
|
1141
|
+
const publicKey = createPublicKey({ key: keys[0], format: "jwk" });
|
|
1142
|
+
if (!verify("RSA-SHA256", Buffer.from(`${parts[0]}.${parts[1]}`), publicKey, Buffer.from(parts[2], "base64url")))
|
|
1143
|
+
return invalid();
|
|
1144
|
+
} catch {
|
|
1145
|
+
return invalid();
|
|
1146
|
+
}
|
|
1147
|
+
const aud = payload.aud;
|
|
1148
|
+
const now = expected.now ?? Date.now();
|
|
1149
|
+
if (payload.iss !== SIWC_ISSUER || !(aud === expected.clientId || Array.isArray(aud) && aud.every((item) => typeof item === "string") && aud.includes(expected.clientId)) || Array.isArray(aud) && aud.length > 1 && payload.azp !== expected.clientId || payload.azp !== undefined && payload.azp !== expected.clientId || typeof payload.exp !== "number" || !Number.isFinite(payload.exp) || !Number.isFinite(now) || payload.exp * 1000 <= now || payload.nbf !== undefined && (typeof payload.nbf !== "number" || !Number.isFinite(payload.nbf) || payload.nbf * 1000 > now) || typeof payload.sub !== "string" || !payload.sub || payload.sub.length > 512 || payload.sub.trim() !== payload.sub || /[\u0000-\u001f\u007f]/.test(payload.sub) || expected.nonce !== undefined && payload.nonce !== expected.nonce || expected.subject !== undefined && payload.sub !== expected.subject)
|
|
1150
|
+
return invalid();
|
|
1151
|
+
return { subject: payload.sub, email: typeof payload.email === "string" && payload.email.length <= 512 && !/[\u0000-\u001f\u007f]/.test(payload.email) ? payload.email : undefined };
|
|
1152
|
+
}
|
|
1153
|
+
function tokenString(record, key, fallback) {
|
|
1154
|
+
if (record[key] === undefined && fallback !== undefined)
|
|
1155
|
+
return fallback;
|
|
1156
|
+
const value = record[key];
|
|
1157
|
+
if (typeof value !== "string" || !value || value.trim() !== value || value.length > 32768 || /[\r\n\0]/.test(value))
|
|
1158
|
+
return invalid();
|
|
1159
|
+
return value;
|
|
1160
|
+
}
|
|
1161
|
+
async function siwcTokenSet(record, expected, options, previous) {
|
|
1162
|
+
const accessToken = tokenString(record, "access_token");
|
|
1163
|
+
const refreshToken = tokenString(record, "refresh_token", previous?.refreshToken);
|
|
1164
|
+
const idToken = tokenString(record, "id_token", previous?.idToken);
|
|
1165
|
+
if (record.token_type !== undefined && (typeof record.token_type !== "string" || record.token_type.toLowerCase() !== "bearer"))
|
|
1166
|
+
return invalid();
|
|
1167
|
+
if (typeof record.expires_in !== "number" || !Number.isFinite(record.expires_in) || record.expires_in <= 0 || record.expires_in * 1000 > Number.MAX_SAFE_INTEGER - Date.now())
|
|
1168
|
+
return invalid();
|
|
1169
|
+
const scopes = record.scope === undefined && previous?.siwc ? [...previous.siwc.scopes] : tokenString(record, "scope").split(/\s+/);
|
|
1170
|
+
const verified = record.id_token === undefined && previous?.siwc ? { subject: previous.siwc.subject, email: previous.identity?.email } : await verifySiwcIdToken(idToken, expected, options);
|
|
1171
|
+
const siwc = { clientId: expected.clientId, subject: verified.subject, scopes };
|
|
1172
|
+
if (!validSiwcMetadata(siwc))
|
|
1173
|
+
return invalid();
|
|
1174
|
+
return {
|
|
1175
|
+
accessToken,
|
|
1176
|
+
refreshToken,
|
|
1177
|
+
idToken,
|
|
1178
|
+
expiresIn: record.expires_in,
|
|
1179
|
+
expiresAt: Date.now() + record.expires_in * 1000,
|
|
1180
|
+
tokenType: "Bearer",
|
|
1181
|
+
identity: { email: verified.email },
|
|
1182
|
+
identityStatus: "parsed",
|
|
1183
|
+
siwc
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
async function exchangeSiwcCode(code, codeVerifier, clientId, nonce, options = {}) {
|
|
1187
|
+
if (!code || !codeVerifier || !nonce || !CLIENT_ID.test(clientId))
|
|
1188
|
+
return invalid();
|
|
1189
|
+
const body = new URLSearchParams({ grant_type: "authorization_code", client_id: clientId, code, code_verifier: codeVerifier, redirect_uri: SIWC_REDIRECT_URI, resource: SIWC_RESOURCE });
|
|
1190
|
+
const record = await requestJson(SIWC_TOKEN_URL, { method: "POST", headers: { accept: "application/json", "content-type": "application/x-www-form-urlencoded" }, body: body.toString() }, options);
|
|
1191
|
+
return siwcTokenSet(record, { clientId, nonce }, options);
|
|
1192
|
+
}
|
|
1193
|
+
async function refreshSiwcToken(previous, options = {}) {
|
|
1194
|
+
if (!validSiwcMetadata(previous.siwc) || !previous.refreshToken)
|
|
1195
|
+
return invalid();
|
|
1196
|
+
const body = new URLSearchParams({ grant_type: "refresh_token", client_id: previous.siwc.clientId, refresh_token: previous.refreshToken, resource: SIWC_RESOURCE });
|
|
1197
|
+
const record = await requestJson(SIWC_TOKEN_URL, { method: "POST", headers: { accept: "application/json", "content-type": "application/x-www-form-urlencoded" }, body: body.toString() }, options, true);
|
|
1198
|
+
return siwcTokenSet(record, { clientId: previous.siwc.clientId, subject: previous.siwc.subject }, options, previous);
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1028
1201
|
// plugins/chatgpt-oauth/server/accounts.ts
|
|
1029
|
-
import { randomUUID } from "crypto";
|
|
1202
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1030
1203
|
var ACCOUNT_STORE_KEY = "accounts.v1";
|
|
1031
1204
|
var ACCOUNT_SCHEMA_VERSION = 1;
|
|
1032
1205
|
|
|
@@ -1059,6 +1232,7 @@ function copyIdentity(value) {
|
|
|
1059
1232
|
function copyAccount(value) {
|
|
1060
1233
|
return {
|
|
1061
1234
|
...value,
|
|
1235
|
+
siwc: value.siwc && { ...value.siwc, scopes: [...value.siwc.scopes] },
|
|
1062
1236
|
identity: copyIdentity(value.identity),
|
|
1063
1237
|
refreshLock: value.refreshLock && { ...value.refreshLock },
|
|
1064
1238
|
autoResetAttempts: value.autoResetAttempts?.map((item) => ({ ...item }))
|
|
@@ -1071,7 +1245,7 @@ function validStoredAccount(value) {
|
|
|
1071
1245
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
1072
1246
|
return false;
|
|
1073
1247
|
const account = value;
|
|
1074
|
-
return typeof account.id === "string" && account.id.length > 0 && account.id.length <= 128 && typeof account.label === "string" && account.label.length > 0 && account.label.length <= MAX_TEXT && ["active", "disabled", "reauth_required", "revoked"].includes(account.status) && Number.isSafeInteger(account.generation) && account.generation > 0 && (account.loginFence === undefined || Number.isSafeInteger(account.loginFence) && account.loginFence > 0) && (account.credentialValid === undefined || typeof account.credentialValid === "boolean") && (account.autoResetCredits === undefined || typeof account.autoResetCredits === "boolean") && validAutoResetAttempts(account.autoResetAttempts) && Number.isFinite(account.createdAt) && Number.isFinite(account.updatedAt);
|
|
1248
|
+
return typeof account.id === "string" && account.id.length > 0 && account.id.length <= 128 && typeof account.label === "string" && account.label.length > 0 && account.label.length <= MAX_TEXT && ["active", "disabled", "reauth_required", "revoked"].includes(account.status) && Number.isSafeInteger(account.generation) && account.generation > 0 && (account.loginFence === undefined || Number.isSafeInteger(account.loginFence) && account.loginFence > 0) && (account.credentialValid === undefined || typeof account.credentialValid === "boolean") && (account.autoResetCredits === undefined || typeof account.autoResetCredits === "boolean") && (account.siwc === undefined || validSiwcMetadata(account.siwc)) && validAutoResetAttempts(account.autoResetAttempts) && Number.isFinite(account.createdAt) && Number.isFinite(account.updatedAt);
|
|
1075
1249
|
}
|
|
1076
1250
|
function decode(value) {
|
|
1077
1251
|
if (value === undefined)
|
|
@@ -1107,6 +1281,7 @@ function statusReason(account) {
|
|
|
1107
1281
|
function accountListItem(account) {
|
|
1108
1282
|
const pending = account.autoResetAttempts?.find((item) => !item.completed);
|
|
1109
1283
|
return {
|
|
1284
|
+
authType: account.siwc ? "siwc" : "codex",
|
|
1110
1285
|
id: account.id,
|
|
1111
1286
|
label: account.label,
|
|
1112
1287
|
status: account.status,
|
|
@@ -1114,10 +1289,25 @@ function accountListItem(account) {
|
|
|
1114
1289
|
reason: statusReason(account),
|
|
1115
1290
|
expiresAt: account.expiresAt,
|
|
1116
1291
|
identity: copyIdentity(account.identity),
|
|
1117
|
-
autoResetCredits: account.autoResetCredits === true,
|
|
1118
|
-
pendingAutoReset:
|
|
1292
|
+
autoResetCredits: !account.siwc && account.autoResetCredits === true,
|
|
1293
|
+
pendingAutoReset: !account.siwc && pending ? { ...pending } : undefined
|
|
1119
1294
|
};
|
|
1120
1295
|
}
|
|
1296
|
+
function validateTokenIdentity(token) {
|
|
1297
|
+
if (token.identityStatus !== "parsed" || (token.siwc === undefined ? !token.identity?.accountId : !validSiwcMetadata(token.siwc))) {
|
|
1298
|
+
throw new AccountControlError("invalid_identity");
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
function validateSiwcBinding(account, token) {
|
|
1302
|
+
if (Boolean(account.siwc) !== Boolean(token.siwc))
|
|
1303
|
+
throw new AccountControlError("identity_mismatch");
|
|
1304
|
+
if (account.siwc) {
|
|
1305
|
+
if (!validSiwcMetadata(token.siwc) || token.identityStatus !== "parsed")
|
|
1306
|
+
throw new AccountControlError("invalid_identity");
|
|
1307
|
+
if (account.siwc.clientId !== token.siwc.clientId || account.siwc.subject !== token.siwc.subject)
|
|
1308
|
+
throw new AccountControlError("identity_mismatch");
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1121
1311
|
|
|
1122
1312
|
class AccountStore {
|
|
1123
1313
|
secretStore;
|
|
@@ -1152,6 +1342,19 @@ class AccountStore {
|
|
|
1152
1342
|
async list() {
|
|
1153
1343
|
return (await this.read()).aggregate.accounts.map(accountListItem);
|
|
1154
1344
|
}
|
|
1345
|
+
async remove(id) {
|
|
1346
|
+
text(id, 128);
|
|
1347
|
+
await this.mutate((aggregate) => {
|
|
1348
|
+
aggregate.accounts = aggregate.accounts.filter((account) => account.id !== id);
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
async purgeRevoked() {
|
|
1352
|
+
if (!(await this.read()).aggregate.accounts.some((account) => account.status === "revoked"))
|
|
1353
|
+
return;
|
|
1354
|
+
await this.mutate((aggregate) => {
|
|
1355
|
+
aggregate.accounts = aggregate.accounts.filter((account) => account.status !== "revoked");
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1155
1358
|
async get(id) {
|
|
1156
1359
|
const account = (await this.read()).aggregate.accounts.find((item) => item.id === id);
|
|
1157
1360
|
if (!account)
|
|
@@ -1160,14 +1363,13 @@ class AccountStore {
|
|
|
1160
1363
|
}
|
|
1161
1364
|
async create(label, token, canCommit = () => true) {
|
|
1162
1365
|
text(label);
|
|
1163
|
-
|
|
1164
|
-
throw new AccountControlError("invalid_identity");
|
|
1366
|
+
validateTokenIdentity(token);
|
|
1165
1367
|
return this.mutate((aggregate) => {
|
|
1166
1368
|
if (aggregate.accounts.length >= MAX_ACCOUNTS)
|
|
1167
1369
|
throw new AccountControlError("invalid_input");
|
|
1168
1370
|
const now = Date.now();
|
|
1169
1371
|
const account = {
|
|
1170
|
-
id:
|
|
1372
|
+
id: randomUUID2(),
|
|
1171
1373
|
label,
|
|
1172
1374
|
status: token.expiresAt === undefined ? "reauth_required" : "active",
|
|
1173
1375
|
generation: 1,
|
|
@@ -1179,7 +1381,8 @@ class AccountStore {
|
|
|
1179
1381
|
refreshToken: text(token.refreshToken, MAX_TOKEN),
|
|
1180
1382
|
idToken: optionalText(token.idToken, MAX_TOKEN),
|
|
1181
1383
|
expiresAt: token.expiresAt,
|
|
1182
|
-
identity: copyIdentity(token.identity)
|
|
1384
|
+
identity: copyIdentity(token.identity),
|
|
1385
|
+
siwc: token.siwc && { ...token.siwc, scopes: [...token.siwc.scopes] }
|
|
1183
1386
|
};
|
|
1184
1387
|
aggregate.accounts.push(account);
|
|
1185
1388
|
return copyAccount(account);
|
|
@@ -1203,7 +1406,9 @@ class AccountStore {
|
|
|
1203
1406
|
if (token.identity?.accountId && account.identity?.accountId && token.identity.accountId !== account.identity.accountId) {
|
|
1204
1407
|
throw new AccountControlError("identity_mismatch");
|
|
1205
1408
|
}
|
|
1409
|
+
validateSiwcBinding(account, token);
|
|
1206
1410
|
const now = Date.now();
|
|
1411
|
+
account.siwc = token.siwc && { ...token.siwc, scopes: [...token.siwc.scopes] };
|
|
1207
1412
|
account.accessToken = text(token.accessToken, MAX_TOKEN);
|
|
1208
1413
|
account.refreshToken = text(token.refreshToken, MAX_TOKEN);
|
|
1209
1414
|
if (token.idToken !== undefined && token.identityStatus !== "invalid")
|
|
@@ -1245,9 +1450,9 @@ class AccountStore {
|
|
|
1245
1450
|
if (account.generation !== expected.generation || account.loginFence !== expected.loginFence) {
|
|
1246
1451
|
throw new AccountControlError("stale_refresh");
|
|
1247
1452
|
}
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
if (account.identity?.accountId && token.identity
|
|
1453
|
+
validateTokenIdentity(token);
|
|
1454
|
+
validateSiwcBinding(account, token);
|
|
1455
|
+
if (account.identity?.accountId && token.identity?.accountId !== account.identity.accountId) {
|
|
1251
1456
|
throw new AccountControlError("identity_mismatch");
|
|
1252
1457
|
}
|
|
1253
1458
|
account.accessToken = text(token.accessToken, MAX_TOKEN);
|
|
@@ -1264,6 +1469,7 @@ class AccountStore {
|
|
|
1264
1469
|
account.status = "active";
|
|
1265
1470
|
}
|
|
1266
1471
|
account.identity = copyIdentity(token.identity);
|
|
1472
|
+
account.siwc = token.siwc && { ...token.siwc, scopes: [...token.siwc.scopes] };
|
|
1267
1473
|
account.generation += 1;
|
|
1268
1474
|
account.updatedAt = Date.now();
|
|
1269
1475
|
delete account.refreshLock;
|
|
@@ -1275,22 +1481,12 @@ class AccountStore {
|
|
|
1275
1481
|
const account = aggregate.accounts.find((item) => item.id === id);
|
|
1276
1482
|
if (!account)
|
|
1277
1483
|
throw new AccountControlError("not_found");
|
|
1278
|
-
if (account.status === "revoked")
|
|
1279
|
-
if (status === "revoked")
|
|
1280
|
-
return copyAccount(account);
|
|
1484
|
+
if (account.status === "revoked")
|
|
1281
1485
|
throw new AccountControlError("revoked");
|
|
1282
|
-
}
|
|
1283
1486
|
account.status = status;
|
|
1284
1487
|
account.generation += 1;
|
|
1285
1488
|
account.updatedAt = Date.now();
|
|
1286
1489
|
delete account.refreshLock;
|
|
1287
|
-
if (status === "revoked") {
|
|
1288
|
-
delete account.accessToken;
|
|
1289
|
-
delete account.refreshToken;
|
|
1290
|
-
delete account.idToken;
|
|
1291
|
-
delete account.expiresAt;
|
|
1292
|
-
account.credentialValid = false;
|
|
1293
|
-
}
|
|
1294
1490
|
return copyAccount(account);
|
|
1295
1491
|
});
|
|
1296
1492
|
}
|
|
@@ -1316,6 +1512,8 @@ class AccountStore {
|
|
|
1316
1512
|
throw new AccountControlError("not_found");
|
|
1317
1513
|
if (account.status === "revoked")
|
|
1318
1514
|
throw new AccountControlError("revoked");
|
|
1515
|
+
if (account.siwc)
|
|
1516
|
+
throw new AccountControlError("unsupported_operation");
|
|
1319
1517
|
account.autoResetCredits = enabled;
|
|
1320
1518
|
account.updatedAt = Date.now();
|
|
1321
1519
|
return copyAccount(account);
|
|
@@ -1324,7 +1522,7 @@ class AccountStore {
|
|
|
1324
1522
|
async claimAutoReset(id, credit, now) {
|
|
1325
1523
|
return this.mutate((aggregate) => {
|
|
1326
1524
|
const account = aggregate.accounts.find((item) => item.id === id);
|
|
1327
|
-
if (!account || !accountListItem(account).available || account.autoResetCredits !== true)
|
|
1525
|
+
if (!account || account.siwc || !accountListItem(account).available || account.autoResetCredits !== true)
|
|
1328
1526
|
return;
|
|
1329
1527
|
const attempts = account.autoResetAttempts ?? [];
|
|
1330
1528
|
if (attempts.some((item) => !item.completed || item.creditId === credit.id))
|
|
@@ -1332,7 +1530,7 @@ class AccountStore {
|
|
|
1332
1530
|
account.autoResetAttempts = attempts.filter((item) => !item.completed || item.expiresAt === undefined || item.expiresAt > now);
|
|
1333
1531
|
if (account.autoResetAttempts.length >= MAX_AUTO_RESET_ATTEMPTS || credit.expiresAt === undefined || credit.expiresAt <= now)
|
|
1334
1532
|
return;
|
|
1335
|
-
const attempt = { creditId: credit.id, redeemRequestId:
|
|
1533
|
+
const attempt = { creditId: credit.id, redeemRequestId: randomUUID2(), expiresAt: credit.expiresAt, completed: false };
|
|
1336
1534
|
account.autoResetAttempts.push(attempt);
|
|
1337
1535
|
account.updatedAt = Date.now();
|
|
1338
1536
|
return { ...attempt };
|
|
@@ -1343,6 +1541,8 @@ class AccountStore {
|
|
|
1343
1541
|
const account = aggregate.accounts.find((item) => item.id === id);
|
|
1344
1542
|
if (!account)
|
|
1345
1543
|
throw new AccountControlError("not_found");
|
|
1544
|
+
if (account.siwc)
|
|
1545
|
+
throw new AccountControlError("unsupported_operation");
|
|
1346
1546
|
const attempts = account.autoResetAttempts ?? [];
|
|
1347
1547
|
const pending = attempts.find((item) => !item.completed);
|
|
1348
1548
|
if (pending) {
|
|
@@ -1451,7 +1651,7 @@ class AccountStore {
|
|
|
1451
1651
|
}
|
|
1452
1652
|
|
|
1453
1653
|
// plugins/chatgpt-oauth/server/sessions.ts
|
|
1454
|
-
import { randomUUID as
|
|
1654
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
1455
1655
|
class LoginSessionError extends Error {
|
|
1456
1656
|
code;
|
|
1457
1657
|
name = "LoginSessionError";
|
|
@@ -1494,7 +1694,7 @@ class LoginSessionManager {
|
|
|
1494
1694
|
this.hostSignal.addEventListener("abort", onHostAbort, { once: true });
|
|
1495
1695
|
const issuedAt = this.now();
|
|
1496
1696
|
const session = {
|
|
1497
|
-
id:
|
|
1697
|
+
id: randomUUID3(),
|
|
1498
1698
|
kind,
|
|
1499
1699
|
state: "pending",
|
|
1500
1700
|
issuedAt,
|
|
@@ -1582,6 +1782,19 @@ class LoginSessionManager {
|
|
|
1582
1782
|
session.codeVerifier = pkce.codeVerifier;
|
|
1583
1783
|
return { sessionId: session.id, authorizationUrl: buildCodexAuthorizationUrl(pkce), expiresAt: session.expiresAt };
|
|
1584
1784
|
}
|
|
1785
|
+
startSiwc(hostId, accountRef, clientId, fence) {
|
|
1786
|
+
if (accountRef !== undefined && (typeof accountRef !== "string" || !accountRef || accountRef.length > 128))
|
|
1787
|
+
throw new LoginSessionError("invalid_input");
|
|
1788
|
+
const pkce = createPKCE(this.deps.random);
|
|
1789
|
+
const nonce = createSiwcNonce(this.deps.random);
|
|
1790
|
+
const authorizationUrl = buildSiwcAuthorizationUrl(pkce, nonce, hostId, clientId);
|
|
1791
|
+
const session = this.newSession("siwc", accountRef, fence, CODEX_CALLBACK_TTL_MS);
|
|
1792
|
+
session.stateValue = pkce.state;
|
|
1793
|
+
session.codeVerifier = pkce.codeVerifier;
|
|
1794
|
+
session.nonce = nonce;
|
|
1795
|
+
session.clientId = clientId;
|
|
1796
|
+
return { sessionId: session.id, authorizationUrl, expiresAt: session.expiresAt };
|
|
1797
|
+
}
|
|
1585
1798
|
setFence(id, fence) {
|
|
1586
1799
|
const session = this.get(id);
|
|
1587
1800
|
if (session.state !== "pending")
|
|
@@ -1694,6 +1907,24 @@ class LoginSessionManager {
|
|
|
1694
1907
|
throw error;
|
|
1695
1908
|
}
|
|
1696
1909
|
}
|
|
1910
|
+
async completeSiwc(id, callbackUrl, commit) {
|
|
1911
|
+
const session = this.claim(id, "siwc");
|
|
1912
|
+
try {
|
|
1913
|
+
const callback = parseSiwcCallbackUrl(callbackUrl, { expectedState: session.stateValue, expectedClientId: session.clientId, issuedAt: session.issuedAt, now: this.now() });
|
|
1914
|
+
const token = await exchangeSiwcCode(callback.code, session.codeVerifier, callback.clientId, session.nonce, safeOptions(this.deps, session.controller.signal));
|
|
1915
|
+
return await this.finish(session, token, commit);
|
|
1916
|
+
} catch (error) {
|
|
1917
|
+
if (session.controller.signal.aborted && session.state === "cancelled")
|
|
1918
|
+
throw new LoginSessionError("cancelled");
|
|
1919
|
+
if (session.state === "exchanging") {
|
|
1920
|
+
session.state = session.controller.signal.aborted ? "cancelled" : "failed";
|
|
1921
|
+
session.errorCode = session.controller.signal.aborted ? "cancelled" : "login_failed";
|
|
1922
|
+
if (session.state === "failed")
|
|
1923
|
+
throw new LoginSessionError("failed");
|
|
1924
|
+
}
|
|
1925
|
+
throw error;
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1697
1928
|
cancel(id) {
|
|
1698
1929
|
const session = this.get(id);
|
|
1699
1930
|
if (session.state === "success" || session.state === "cancelled" || session.state === "failed" || session.state === "committing")
|
|
@@ -2462,6 +2693,7 @@ class AutoResetScheduler {
|
|
|
2462
2693
|
|
|
2463
2694
|
// plugins/chatgpt-oauth/server/control.ts
|
|
2464
2695
|
var CHATGPT_TARGET = "https://chatgpt.com";
|
|
2696
|
+
var SIWC_TARGET = "https://api.openai.com";
|
|
2465
2697
|
var CONTROL_HOST_CONTRACT_GAP = "ControlRpcContext.binding.bindingOptions.accountRef is required and host-resolved; RPC payload is never used as a fallback.";
|
|
2466
2698
|
|
|
2467
2699
|
class ControlError extends Error {
|
|
@@ -2488,7 +2720,8 @@ function safeError(error) {
|
|
|
2488
2720
|
identity_mismatch: "identity_mismatch",
|
|
2489
2721
|
invalid_identity: "invalid_identity",
|
|
2490
2722
|
disposed: "disposed",
|
|
2491
|
-
stale_refresh: "stale_refresh"
|
|
2723
|
+
stale_refresh: "stale_refresh",
|
|
2724
|
+
unsupported_operation: "unsupported_operation"
|
|
2492
2725
|
};
|
|
2493
2726
|
return new ControlError(map[error.code] ?? "refresh_failed");
|
|
2494
2727
|
}
|
|
@@ -2653,6 +2886,11 @@ function lease(account) {
|
|
|
2653
2886
|
throw new ControlError(account.status);
|
|
2654
2887
|
if (!account.accessToken)
|
|
2655
2888
|
throw new ControlError("reauth_required");
|
|
2889
|
+
if (account.siwc) {
|
|
2890
|
+
if (!validSiwcMetadata(account.siwc) || !Number.isFinite(account.expiresAt))
|
|
2891
|
+
throw new ControlError("reauth_required");
|
|
2892
|
+
return { version: account.generation, expiresAt: account.expiresAt, headers: { Authorization: `Bearer ${account.accessToken}` } };
|
|
2893
|
+
}
|
|
2656
2894
|
if (!account.identity?.accountId)
|
|
2657
2895
|
throw new ControlError("identity_missing");
|
|
2658
2896
|
if (!Number.isFinite(account.expiresAt))
|
|
@@ -2671,7 +2909,7 @@ class ChatgptControl {
|
|
|
2671
2909
|
accounts;
|
|
2672
2910
|
sessions;
|
|
2673
2911
|
now;
|
|
2674
|
-
workerOwner = `chatgpt-control-${
|
|
2912
|
+
workerOwner = `chatgpt-control-${randomUUID4()}`;
|
|
2675
2913
|
inFlight = new Map;
|
|
2676
2914
|
refreshControllers = new Map;
|
|
2677
2915
|
apiLifetime = new AbortController;
|
|
@@ -2690,7 +2928,7 @@ class ChatgptControl {
|
|
|
2690
2928
|
now: this.now,
|
|
2691
2929
|
timeoutMs: deps.timeoutMs,
|
|
2692
2930
|
hostSignal: this.apiLifetime.signal,
|
|
2693
|
-
credential: (id, signal) => this.credential(id, signal),
|
|
2931
|
+
credential: (id, signal) => this.credential(id, signal, "codex"),
|
|
2694
2932
|
rejectAccess: (id, version) => this.accounts.rejectAccess(id, version)
|
|
2695
2933
|
});
|
|
2696
2934
|
this.autoReset = new AutoResetScheduler(this.accounts, this.usage, this.apiLifetime.signal, this.now, deps.autoResetTimer);
|
|
@@ -2718,7 +2956,7 @@ class ChatgptControl {
|
|
|
2718
2956
|
}
|
|
2719
2957
|
async start() {
|
|
2720
2958
|
this.assertAlive();
|
|
2721
|
-
await this.accounts.
|
|
2959
|
+
await this.accounts.purgeRevoked();
|
|
2722
2960
|
this.assertAlive();
|
|
2723
2961
|
this.autoReset.start();
|
|
2724
2962
|
}
|
|
@@ -2741,7 +2979,8 @@ class ChatgptControl {
|
|
|
2741
2979
|
}
|
|
2742
2980
|
const generation = lock.account.generation;
|
|
2743
2981
|
try {
|
|
2744
|
-
const
|
|
2982
|
+
const refreshOptions = { fetchImpl: this.deps.fetchImpl, signal, timeoutMs: this.deps.timeoutMs };
|
|
2983
|
+
const token = lock.account.siwc ? await refreshSiwcToken({ accessToken: lock.account.accessToken, refreshToken: lock.account.refreshToken, idToken: lock.account.idToken, identity: lock.account.identity, identityStatus: "parsed", siwc: lock.account.siwc }, refreshOptions) : await refreshCodexToken(lock.account.refreshToken, refreshOptions);
|
|
2745
2984
|
this.assertAlive(signal);
|
|
2746
2985
|
const updated = await this.accounts.replaceCredentials(id, token, { owner: this.workerOwner, generation });
|
|
2747
2986
|
this.assertAlive(signal);
|
|
@@ -2750,12 +2989,14 @@ class ChatgptControl {
|
|
|
2750
2989
|
if (this.disposed || this.host.signal.aborted)
|
|
2751
2990
|
throw new ControlError("disposed");
|
|
2752
2991
|
const oauthCode = error.kind;
|
|
2753
|
-
if (oauthCode === "refresh_token_reused") {
|
|
2992
|
+
if (oauthCode === "refresh_token_reused" || lock.account.siwc && (error.upstreamCode === "invalid_grant" || oauthCode === "invalid_response" || error instanceof AccountControlError && ["identity_mismatch", "invalid_identity"].includes(error.code))) {
|
|
2754
2993
|
const fenced = await this.accounts.failRefreshReauth(id, this.workerOwner, generation).catch(() => false);
|
|
2755
2994
|
if (!fenced) {
|
|
2756
2995
|
const latest = await this.accounts.get(id).catch(() => {
|
|
2757
2996
|
return;
|
|
2758
2997
|
});
|
|
2998
|
+
if (!latest)
|
|
2999
|
+
throw new ControlError("not_found");
|
|
2759
3000
|
if (latest?.status === "revoked")
|
|
2760
3001
|
throw new ControlError("revoked");
|
|
2761
3002
|
if (latest?.status === "disabled")
|
|
@@ -2772,6 +3013,8 @@ class ChatgptControl {
|
|
|
2772
3013
|
const latest = await this.accounts.get(id).catch(() => {
|
|
2773
3014
|
return;
|
|
2774
3015
|
});
|
|
3016
|
+
if (!latest)
|
|
3017
|
+
throw new ControlError("not_found");
|
|
2775
3018
|
if (latest?.status === "revoked")
|
|
2776
3019
|
throw new ControlError("revoked");
|
|
2777
3020
|
if (latest?.status === "disabled")
|
|
@@ -2786,10 +3029,38 @@ class ChatgptControl {
|
|
|
2786
3029
|
}
|
|
2787
3030
|
throw new ControlError("refresh_in_progress");
|
|
2788
3031
|
}
|
|
2789
|
-
async
|
|
3032
|
+
async requireKind(id, kind, error = "source_mismatch") {
|
|
3033
|
+
const account = await this.accounts.get(id);
|
|
3034
|
+
if (Boolean(account.siwc) !== (kind === "siwc"))
|
|
3035
|
+
throw new ControlError(error);
|
|
3036
|
+
return account;
|
|
3037
|
+
}
|
|
3038
|
+
async bindingAccount(context) {
|
|
3039
|
+
const id = accountRefFromBinding(context);
|
|
3040
|
+
const source = context.binding.contributionId;
|
|
3041
|
+
if (source === "chatgpt-siwc")
|
|
3042
|
+
await this.requireKind(id, "siwc");
|
|
3043
|
+
else if (source === "chatgpt" || source === "upstream")
|
|
3044
|
+
await this.requireKind(id, "codex");
|
|
3045
|
+
else
|
|
3046
|
+
throw new ControlError("source_mismatch");
|
|
3047
|
+
return id;
|
|
3048
|
+
}
|
|
3049
|
+
async draft(context, kind) {
|
|
3050
|
+
const body = await this.readJson(context);
|
|
3051
|
+
exactKeys(body, ["accountRef"]);
|
|
3052
|
+
const account = await this.requireKind(requiredText(body.accountRef, 128), kind);
|
|
3053
|
+
if (!accountListItem(account).available)
|
|
3054
|
+
throw new ControlError(account.status === "active" ? "reauth_required" : account.status);
|
|
3055
|
+
const draft = { target: kind === "siwc" ? SIWC_TARGET : CHATGPT_TARGET, bindingOptions: { accountRef: account.id } };
|
|
3056
|
+
return jsonResponse(draft);
|
|
3057
|
+
}
|
|
3058
|
+
async credential(id, signal, kind) {
|
|
2790
3059
|
this.assertAlive(signal);
|
|
2791
3060
|
const account = await this.accounts.get(id);
|
|
2792
3061
|
this.assertAlive(signal);
|
|
3062
|
+
if (kind === "codex" && account.siwc)
|
|
3063
|
+
throw new ControlError("unsupported_operation");
|
|
2793
3064
|
if (account.status !== "active")
|
|
2794
3065
|
throw new ControlError(account.status);
|
|
2795
3066
|
if (!isRefreshable(account, this.now())) {
|
|
@@ -2830,6 +3101,8 @@ class ChatgptControl {
|
|
|
2830
3101
|
return readRequestJson(context.request, context.requestSignal, context.signal, this.apiLifetime.signal);
|
|
2831
3102
|
}
|
|
2832
3103
|
async startDevice(accountRef) {
|
|
3104
|
+
if (accountRef !== undefined)
|
|
3105
|
+
await this.requireKind(accountRef, "codex");
|
|
2833
3106
|
const started = await this.sessions.startDevice(accountRef, () => this.loginFence(accountRef));
|
|
2834
3107
|
this.sessions.completeDevice(started.sessionId, (token, info, guard) => this.commitLogin(token, info, guard)).catch(() => {
|
|
2835
3108
|
return;
|
|
@@ -2854,11 +3127,17 @@ class ChatgptControl {
|
|
|
2854
3127
|
};
|
|
2855
3128
|
return [
|
|
2856
3129
|
{ path: "/accounts", methods: ["GET"], handler: "listAccounts", invoke: invoke(async () => jsonResponse({ accounts: await this.accounts.list() })) },
|
|
2857
|
-
{ path: "/accounts/
|
|
3130
|
+
{ path: "/accounts/codex", methods: ["GET"], handler: "listCodexAccounts", invoke: invoke(async () => jsonResponse({ accounts: (await this.accounts.list()).filter((account) => account.authType === "codex") })) },
|
|
3131
|
+
{ path: "/accounts/siwc", methods: ["GET"], handler: "listSiwcAccounts", invoke: invoke(async () => jsonResponse({ accounts: (await this.accounts.list()).filter((account) => account.authType === "siwc") })) },
|
|
3132
|
+
{ path: "/accounts/usage", methods: ["GET"], handler: "getAccountUsage", invoke: invoke(async (context) => {
|
|
3133
|
+
const id = this.accountRefQuery(context.request);
|
|
3134
|
+
return jsonResponse(await this.usage.get(id, context.requestSignal));
|
|
3135
|
+
}) },
|
|
2858
3136
|
{ path: "/accounts/usage/reset", methods: ["POST"], handler: "resetAccountUsage", invoke: invoke(async (context) => {
|
|
2859
3137
|
const body = await this.readJson(context);
|
|
2860
3138
|
exactKeys(body, ["accountRef", "redeemRequestId", "creditId"]);
|
|
2861
3139
|
const accountRef = requiredText(body.accountRef, 128);
|
|
3140
|
+
await this.requireKind(accountRef, "codex", "unsupported_operation");
|
|
2862
3141
|
const redeemRequestId = requiredText(body.redeemRequestId, 128);
|
|
2863
3142
|
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(redeemRequestId))
|
|
2864
3143
|
throw new ControlError("invalid_input");
|
|
@@ -2885,14 +3164,18 @@ class ChatgptControl {
|
|
|
2885
3164
|
throw new ControlError("invalid_input");
|
|
2886
3165
|
return jsonResponse({ account: accountListItem(await this.accounts.setAutoResetCredits(requiredText(body.accountRef, 128), body.enabled)) });
|
|
2887
3166
|
}) },
|
|
2888
|
-
{ path: "/accounts/draft", methods: ["POST"], handler: "createDraft", invoke: invoke(
|
|
3167
|
+
{ path: "/accounts/draft", methods: ["POST"], handler: "createDraft", invoke: invoke((context) => this.draft(context, "codex")) },
|
|
3168
|
+
{ path: "/accounts/siwc/draft", methods: ["POST"], handler: "createSiwcDraft", invoke: invoke((context) => this.draft(context, "siwc")) },
|
|
3169
|
+
{ path: "/login/siwc", methods: ["POST"], handler: "startSiwcLogin", invoke: invoke(async (context) => {
|
|
2889
3170
|
const body = await this.readJson(context);
|
|
2890
3171
|
exactKeys(body, ["accountRef"]);
|
|
2891
|
-
const
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
3172
|
+
const accountRef = optionalText3(body.accountRef, 128);
|
|
3173
|
+
const account = accountRef === undefined ? undefined : await this.requireKind(accountRef, "siwc");
|
|
3174
|
+
const hostId = await getSiwcHostId(this.host.secretStore, () => !this.disposed && !this.host.signal.aborted);
|
|
3175
|
+
this.assertAlive(context.requestSignal);
|
|
3176
|
+
const fence = await this.loginFence(accountRef);
|
|
3177
|
+
this.assertAlive(context.requestSignal);
|
|
3178
|
+
return jsonResponse(this.sessions.startSiwc(hostId, accountRef, account?.siwc?.clientId, fence));
|
|
2896
3179
|
}) },
|
|
2897
3180
|
{ path: "/login/device", methods: ["POST"], handler: "startDeviceLogin", invoke: invoke(async (context) => {
|
|
2898
3181
|
const body = await this.readJson(context);
|
|
@@ -2903,6 +3186,8 @@ class ChatgptControl {
|
|
|
2903
3186
|
const body = await this.readJson(context);
|
|
2904
3187
|
exactKeys(body, ["accountRef"]);
|
|
2905
3188
|
const accountRef = optionalText3(body.accountRef, 128);
|
|
3189
|
+
if (accountRef !== undefined)
|
|
3190
|
+
await this.requireKind(accountRef, "codex");
|
|
2906
3191
|
const started = this.sessions.startPkce(accountRef);
|
|
2907
3192
|
try {
|
|
2908
3193
|
if (accountRef !== undefined)
|
|
@@ -2927,7 +3212,8 @@ class ChatgptControl {
|
|
|
2927
3212
|
const status = this.sessions.status(sessionId);
|
|
2928
3213
|
if (status.state !== "pending")
|
|
2929
3214
|
return jsonResponse(status);
|
|
2930
|
-
const
|
|
3215
|
+
const complete = status.kind === "siwc" ? this.sessions.completeSiwc.bind(this.sessions) : this.sessions.completePkce.bind(this.sessions);
|
|
3216
|
+
const result = await complete(sessionId, requiredText(body.callbackUrl, 4096), (token, info, guard) => this.commitLogin(token, info, guard));
|
|
2931
3217
|
return jsonResponse({ account: result.result });
|
|
2932
3218
|
}) },
|
|
2933
3219
|
{ path: "/login/device/complete", methods: ["POST"], handler: "completeDeviceLogin", invoke: invoke(async (context) => {
|
|
@@ -2969,9 +3255,9 @@ class ChatgptControl {
|
|
|
2969
3255
|
const body = await this.readJson(context);
|
|
2970
3256
|
exactKeys(body, ["accountRef"]);
|
|
2971
3257
|
const accountRef = requiredText(body.accountRef, 128);
|
|
2972
|
-
|
|
3258
|
+
await this.accounts.remove(accountRef);
|
|
2973
3259
|
this.usage.invalidateAccount(accountRef);
|
|
2974
|
-
return jsonResponse({
|
|
3260
|
+
return jsonResponse({ deleted: true });
|
|
2975
3261
|
}) }
|
|
2976
3262
|
];
|
|
2977
3263
|
}
|
|
@@ -2980,7 +3266,7 @@ class ChatgptControl {
|
|
|
2980
3266
|
{ name: "getCredential", handler: "getCredential", invoke: async (_payload, context) => {
|
|
2981
3267
|
try {
|
|
2982
3268
|
this.assertAlive(context.attempt.signal);
|
|
2983
|
-
return await this.credential(
|
|
3269
|
+
return await this.credential(await this.bindingAccount(context), context.attempt.signal);
|
|
2984
3270
|
} catch (error) {
|
|
2985
3271
|
throw safeError(error);
|
|
2986
3272
|
}
|
|
@@ -2990,7 +3276,7 @@ class ChatgptControl {
|
|
|
2990
3276
|
this.assertAlive(context.attempt.signal);
|
|
2991
3277
|
const body = record(payload);
|
|
2992
3278
|
exactKeys(body, ["version"]);
|
|
2993
|
-
return { rejected: await this.accounts.rejectAccess(
|
|
3279
|
+
return { rejected: await this.accounts.rejectAccess(await this.bindingAccount(context), requiredVersion(body.version)) };
|
|
2994
3280
|
} catch (error) {
|
|
2995
3281
|
throw safeError(error);
|
|
2996
3282
|
}
|
|
@@ -3016,10 +3302,14 @@ function createControl(context, dependencies = {}) {
|
|
|
3016
3302
|
}
|
|
3017
3303
|
var api = Object.freeze([
|
|
3018
3304
|
{ path: "/accounts", methods: ["GET"], handler: "listAccounts" },
|
|
3305
|
+
{ path: "/accounts/codex", methods: ["GET"], handler: "listCodexAccounts" },
|
|
3306
|
+
{ path: "/accounts/siwc", methods: ["GET"], handler: "listSiwcAccounts" },
|
|
3019
3307
|
{ path: "/accounts/usage", methods: ["GET"], handler: "getAccountUsage" },
|
|
3020
3308
|
{ path: "/accounts/usage/reset", methods: ["POST"], handler: "resetAccountUsage" },
|
|
3021
3309
|
{ path: "/accounts/auto-reset", methods: ["POST"], handler: "setAutoResetCredits" },
|
|
3022
3310
|
{ path: "/accounts/draft", methods: ["POST"], handler: "createDraft" },
|
|
3311
|
+
{ path: "/accounts/siwc/draft", methods: ["POST"], handler: "createSiwcDraft" },
|
|
3312
|
+
{ path: "/login/siwc", methods: ["POST"], handler: "startSiwcLogin" },
|
|
3023
3313
|
{ path: "/login/device", methods: ["POST"], handler: "startDeviceLogin" },
|
|
3024
3314
|
{ path: "/login/pkce", methods: ["POST"], handler: "startPkceLogin" },
|
|
3025
3315
|
{ path: "/login/status", methods: ["GET"], handler: "getLoginStatus" },
|
|
@@ -3039,6 +3329,7 @@ export {
|
|
|
3039
3329
|
CHATGPT_TARGET,
|
|
3040
3330
|
CONTROL_HOST_CONTRACT_GAP,
|
|
3041
3331
|
ControlError,
|
|
3332
|
+
SIWC_TARGET,
|
|
3042
3333
|
api,
|
|
3043
3334
|
controlApi,
|
|
3044
3335
|
controlRpc,
|
|
@@ -3047,4 +3338,4 @@ export {
|
|
|
3047
3338
|
rpc
|
|
3048
3339
|
};
|
|
3049
3340
|
|
|
3050
|
-
//# debugId=
|
|
3341
|
+
//# debugId=08B71A7DC584C3AB64756E2164756E21
|