diff --git a/.github/workflows/pr-target-check.yml b/.github/workflows/pr-target-check.yml new file mode 100644 index 0000000..9813c88 --- /dev/null +++ b/.github/workflows/pr-target-check.yml @@ -0,0 +1,41 @@ +name: PR Target Check + +on: + pull_request_target: + branches: + - main + +jobs: + warn-wrong-target: + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Comment and close PR targeting main + uses: actions/github-script@v7 + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `👋 Hey @${context.payload.pull_request.user.login}, thanks for your contribution! + +` + + `This PR is targeting \`main\` directly. We use \`main\` for stable releases only — ` + + `all contributions should be opened against the \`dev\` branch instead. + +` + + `**Please close this PR and reopen it with \`dev\` as the base branch.** ` + + `If you're unsure how to do that, you can change the base branch using the *Edit* button at the top of this PR page. + +` + + `Closing this PR automatically. See you in \`dev\`! 🚀` + }); + + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + state: 'closed' + }); diff --git a/tests/integration/auth-jwt-validation.test.ts b/tests/integration/auth-jwt-validation.test.ts index e9b524b..51e2b74 100644 --- a/tests/integration/auth-jwt-validation.test.ts +++ b/tests/integration/auth-jwt-validation.test.ts @@ -138,7 +138,6 @@ describe("JWT authentication validation", () => { it("rejects GET /api/v1/auth/me when the JWT is signed with an invalid secret key", async () => { const app = createTestApp(); - // Forge a token signed with the wrong secret const forgedToken = jwt.sign( { sub: "GFORGED_STELLAR_ADDRESS", @@ -161,4 +160,43 @@ describe("JWT authentication validation", () => { }, }); }); + + it("rejects GET /api/v1/auth/me with expired JWT token", async () => { + const app = createTestApp(); + + const expiredToken = jwt.sign( + { + sub: "GEXPIRED_STELLAR_ADDRESS", + stellarAddress: "GEXPIRED_STELLAR_ADDRESS", + userId: crypto.randomUUID(), + }, + VALID_JWT_SECRET, + { expiresIn: "-5m" }, + ); + + const response = await request(app) + .get("/api/v1/auth/me") + .set("Authorization", `Bearer ${expiredToken}`) + .expect(401); + + expect(response.body).toMatchObject({ + success: false, + error: { + message: "Invalid or expired token.", + }, + }); + }); + + it("returns 401 from /me when the bearer token is missing", async () => { + const app = createTestApp(); + + const response = await request(app).get("/api/v1/auth/me").expect(401); + + expect(response.body).toMatchObject({ + success: false, + error: { + message: "Authorization token is required.", + }, + }); + }); });