@remit/backend 0.0.77 → 0.0.79
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/package.json
CHANGED
package/src/auth.test.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import { deriveAccountConfigId } from "./auth.js";
|
|
4
|
+
|
|
5
|
+
describe("derived ids are stored primary keys: a changed value orphans every row already written and needs a migration, never a new expectation here", () => {
|
|
6
|
+
test("deriveAccountConfigId pins the account a Cognito subject resolves to", () => {
|
|
7
|
+
assert.equal(
|
|
8
|
+
deriveAccountConfigId("golden-cognito-sub"),
|
|
9
|
+
"97h2fhd0c32ocenk6ju12oxto",
|
|
10
|
+
);
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import {
|
|
3
|
+
existsSync,
|
|
3
4
|
mkdirSync,
|
|
4
5
|
mkdtempSync,
|
|
5
6
|
readFileSync,
|
|
@@ -71,6 +72,11 @@ const buildEvent = (sub?: string): APIGatewayProxyEvent =>
|
|
|
71
72
|
requestContext: sub ? { authorizer: { claims: { sub } } } : {},
|
|
72
73
|
}) as unknown as APIGatewayProxyEvent;
|
|
73
74
|
|
|
75
|
+
// The query as openapi-backend hands it over: validated and coerced against the
|
|
76
|
+
// spec, so `refresh` reaches the handler as a boolean or not at all.
|
|
77
|
+
const getContext = (query: Record<string, unknown> = {}): Context =>
|
|
78
|
+
({ request: { query } }) as unknown as Context;
|
|
79
|
+
|
|
74
80
|
const postContext = (targetVersion: string): Context =>
|
|
75
81
|
({ request: { requestBody: { targetVersion } } }) as unknown as Context;
|
|
76
82
|
|
|
@@ -86,8 +92,10 @@ const applySystemUpdate =
|
|
|
86
92
|
event: APIGatewayProxyEvent,
|
|
87
93
|
) => Promise<unknown>;
|
|
88
94
|
|
|
89
|
-
const getUpdate = (
|
|
90
|
-
|
|
95
|
+
const getUpdate = (
|
|
96
|
+
event: APIGatewayProxyEvent,
|
|
97
|
+
query: Record<string, unknown> = {},
|
|
98
|
+
) => getSystemUpdate(getContext(query), event);
|
|
91
99
|
|
|
92
100
|
const applyUpdate = (targetVersion: string, event: APIGatewayProxyEvent) =>
|
|
93
101
|
applySystemUpdate(postContext(targetVersion), event);
|
|
@@ -130,6 +138,49 @@ describe("GET /system/update", () => {
|
|
|
130
138
|
assert.deepEqual(result, okState);
|
|
131
139
|
});
|
|
132
140
|
|
|
141
|
+
it("records a check request on the control volume when refresh is set (#599)", async () => {
|
|
142
|
+
writeState(okState);
|
|
143
|
+
|
|
144
|
+
const result = await getUpdate(buildEvent(USER), { refresh: true });
|
|
145
|
+
|
|
146
|
+
// The press reaches the updater through the seam, and the answer is the
|
|
147
|
+
// stored state — lastCheckedAt included, so the panel can say how old the
|
|
148
|
+
// verdict it is still showing is.
|
|
149
|
+
const file = readFileSync(join(controlDir, "check-request.json"), "utf8");
|
|
150
|
+
assert.deepEqual(JSON.parse(file), {});
|
|
151
|
+
assert.deepEqual(result, okState);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("records a check request over an unknown version when no state exists yet", async () => {
|
|
155
|
+
const result = await getUpdate(buildEvent(USER), { refresh: true });
|
|
156
|
+
|
|
157
|
+
assert.equal(existsSync(join(controlDir, "check-request.json")), true);
|
|
158
|
+
assert.deepEqual(result, {
|
|
159
|
+
currentVersion: "unknown",
|
|
160
|
+
check: { status: "disabled" },
|
|
161
|
+
run: null,
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("records nothing when the query carries no refresh", async () => {
|
|
166
|
+
// `?refresh=1` is rejected by the spec before it reaches here, so the only
|
|
167
|
+
// value that records a request is the boolean the validator produced.
|
|
168
|
+
writeState(okState);
|
|
169
|
+
|
|
170
|
+
await getUpdate(buildEvent(USER));
|
|
171
|
+
|
|
172
|
+
assert.equal(existsSync(join(controlDir, "check-request.json")), false);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("returns 401 and records nothing when a refresh is not authenticated", async () => {
|
|
176
|
+
writeState(okState);
|
|
177
|
+
|
|
178
|
+
const result = await getUpdate(buildEvent(), { refresh: true });
|
|
179
|
+
|
|
180
|
+
assert.ok(hasStatus(result, 401));
|
|
181
|
+
assert.equal(existsSync(join(controlDir, "check-request.json")), false);
|
|
182
|
+
});
|
|
183
|
+
|
|
133
184
|
it("reports an unknown version, not its own process env, when no state file exists", async () => {
|
|
134
185
|
// The updater owns the running version and writes it into state.json; with
|
|
135
186
|
// no state file the backend has nothing authoritative, so it says unknown
|
|
@@ -112,6 +112,20 @@ const writeRequest = (request: {
|
|
|
112
112
|
renameSync(tmp, join(dir, "request.json"));
|
|
113
113
|
};
|
|
114
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Record a check request on the control seam (#599). The updater consumes it on
|
|
117
|
+
* its watch loop and runs a manifest check immediately, so a press of check in
|
|
118
|
+
* the panel — not just the updater's own cadence — moves `lastCheckedAt`. Like
|
|
119
|
+
* request.json it is written atomically and carries no authority: its presence
|
|
120
|
+
* is the whole message, so it is empty.
|
|
121
|
+
*/
|
|
122
|
+
const writeCheckRequest = (): void => {
|
|
123
|
+
const dir = controlDir();
|
|
124
|
+
const tmp = join(dir, `.check-request.json.tmp`);
|
|
125
|
+
writeFileSync(tmp, JSON.stringify({}), { mode: 0o644 });
|
|
126
|
+
renameSync(tmp, join(dir, "check-request.json"));
|
|
127
|
+
};
|
|
128
|
+
|
|
115
129
|
/**
|
|
116
130
|
* The resource returned by the POST. The updater has not yet written the
|
|
117
131
|
* authoritative run — it polls the seam — so this bootstraps the run block with
|
|
@@ -148,7 +162,7 @@ export const SystemOperations: Record<
|
|
|
148
162
|
OperationHandler<SystemOperationIds>
|
|
149
163
|
> = {
|
|
150
164
|
SystemOperations_getSystemUpdate: async (
|
|
151
|
-
|
|
165
|
+
context: Context,
|
|
152
166
|
...args: unknown[]
|
|
153
167
|
): Promise<SystemUpdateResponse | APIGatewayProxyResult> => {
|
|
154
168
|
const offSurface = guardManifestConfigured();
|
|
@@ -157,6 +171,14 @@ export const SystemOperations: Record<
|
|
|
157
171
|
const event = args[0] as APIGatewayProxyEvent;
|
|
158
172
|
if (!getSubFromEvent(event)) return unauthorized();
|
|
159
173
|
|
|
174
|
+
// A refresh asks for a fresh answer, and only the updater can fetch one, so
|
|
175
|
+
// the request goes on the control seam for its watch loop to pick up (#599).
|
|
176
|
+
// The answer is the stored state as it stands, `lastCheckedAt` included: the
|
|
177
|
+
// caller holds its own press and watches that timestamp move, so the
|
|
178
|
+
// response never has to claim a verdict the updater has not reached.
|
|
179
|
+
const { refresh } = context.request.query as { refresh?: boolean };
|
|
180
|
+
if (refresh === true) writeCheckRequest();
|
|
181
|
+
|
|
160
182
|
return readState() ?? emptyResource();
|
|
161
183
|
},
|
|
162
184
|
|