mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 05:11:49 +01:00
fix(auth): keep CLI approval links on the public instance origin
Use the existing canonical public origin resolver instead of proxy request headers when building the CLI browser approval URL. Cover upstream forwarded-host replacement with a route regression. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
1b7fe33432
commit
9dd05f2aa3
3 files changed
+30
-3
No files matched your search
+3
-1
@@ -9,7 +9,9 @@ Paperclip CLI now supports both:
|
||||
## Browser-approved Board authentication
|
||||
|
||||
`npx paperclipai auth login --api-base <instance-url>` creates a ten-minute
|
||||
challenge and prints a browser approval link. Sign in to the instance, review
|
||||
challenge and prints a browser approval link. The link uses the instance's
|
||||
canonical public origin when configured, so reverse proxies do not send the
|
||||
browser to an internal upstream hostname. Sign in to the instance, review
|
||||
the requesting command and full Board access, then approve. The pending token
|
||||
cannot authenticate before approval. The CLI verifies its resulting identity
|
||||
before storing its credential in its private auth store. The default Board key
|
||||
|
||||
@@ -53,7 +53,7 @@ function registerModuleMocks() {
|
||||
|
||||
let appImportCounter = 0;
|
||||
|
||||
async function createApp(actor: any, db: any = {} as any, options: { cliAuthCreateRateLimiter?: InviteRateLimiter; cliAuthPollRateLimiter?: InviteRateLimiter } = {}) {
|
||||
async function createApp(actor: any, db: any = {} as any, options: { authPublicBaseUrl?: string; cliAuthCreateRateLimiter?: InviteRateLimiter; cliAuthPollRateLimiter?: InviteRateLimiter } = {}) {
|
||||
appImportCounter += 1;
|
||||
const routeModulePath = `../routes/access.js?cli-auth-routes-${appImportCounter}`;
|
||||
const middlewareModulePath = `../middleware/index.js?cli-auth-routes-${appImportCounter}`;
|
||||
@@ -137,6 +137,31 @@ describe("cli auth routes", () => {
|
||||
expect(res.body.approvalUrl).toContain("/cli-auth/12345678-1234-4123-8123-123456789abc?token=pcp_cli_auth_secret");
|
||||
});
|
||||
|
||||
it("uses the configured public origin for CLI approval behind an upstream proxy", async () => {
|
||||
mockBoardAuthService.createCliAuthChallenge.mockResolvedValue({
|
||||
challenge: {
|
||||
id: "12345678-1234-4123-8123-123456789abc",
|
||||
expiresAt: new Date(Date.now() + 60_000),
|
||||
},
|
||||
challengeSecret: "pcp_cli_auth_secret",
|
||||
pendingBoardToken: "pcp_board_token",
|
||||
});
|
||||
const app = await createApp({ type: "none", source: "none" }, {}, {
|
||||
authPublicBaseUrl: "https://workspace.example.com/",
|
||||
});
|
||||
const res = await request(app)
|
||||
.post("/api/cli-auth/challenges")
|
||||
.set("Host", "upstream.example.net")
|
||||
.set("X-Forwarded-Host", "upstream.example.net")
|
||||
.set("X-Forwarded-Proto", "https")
|
||||
.send({ command: "paperclipai auth login", requestedAccess: "board" });
|
||||
|
||||
expect(res.status).toBe(201);
|
||||
expect(res.body.approvalUrl).toBe(
|
||||
`https://workspace.example.com${res.body.approvalPath}`,
|
||||
);
|
||||
});
|
||||
|
||||
it("bounds anonymous create and poll independently without trusting spoofed forwarding headers", async () => {
|
||||
let now = 1_000;
|
||||
mockBoardAuthService.createCliAuthChallenge.mockResolvedValue({
|
||||
|
||||
@@ -2795,7 +2795,7 @@ export function accessRoutes(
|
||||
created.challenge.id,
|
||||
created.challengeSecret,
|
||||
);
|
||||
const baseUrl = requestBaseUrl(req);
|
||||
const baseUrl = resolveBaseUrl(req, opts.authPublicBaseUrl);
|
||||
res.status(201).json({
|
||||
id: created.challenge.id,
|
||||
token: created.challengeSecret,
|
||||
|
||||
Reference in new issue
Block a user