Skip to content

✨ server: link wa id on registration#1157

Draft
mainqueg wants to merge 3 commits into
mainfrom
link
Draft

✨ server: link wa id on registration#1157
mainqueg wants to merge 3 commits into
mainfrom
link

Conversation

@mainqueg

@mainqueg mainqueg commented Jul 17, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Registration now supports an optional WhatsApp ID link through a secure header.
    • Valid WhatsApp IDs are associated with newly created credentials.
    • Invalid or incorrectly signed links are rejected during registration.
  • Bug Fixes

    • Added validation to prevent malformed or expired WhatsApp linking tokens from being accepted.
  • Tests

    • Added coverage for valid, invalid, expired, and mismatched registration tokens.

@changeset-bot

changeset-bot Bot commented Jul 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 3c4cfe1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@exactly/server Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Changes

WhatsApp ID registration linking

Layer / File(s) Summary
Encrypted token and secret infrastructure
server/utils/token.ts, server/utils/chatSecret.ts, server/vitest.config.mts, server/script/openapi.ts, server/test/utils/token.test.ts
Adds encrypted JWT encoding and decoding with audience, expiration, subject, and secret validation, plus chat identity key configuration and utility coverage.
Credential WhatsApp ID persistence
server/database/schema.ts, server/utils/createCredential.ts
Adds the waId credential column and unique index, and persists the optional decoded WhatsApp ID during credential creation.
Registration endpoint integration
server/api/auth/registration.ts, server/test/api/auth.test.ts, .changeset/quick-otters-link.md
Validates and decodes optional Wa-Id headers for registration flows, returns bad wa for invalid tokens, forwards valid IDs to credential creation, and adds a patch changeset.
Estimated code review effort: 3 (Moderate) ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant RegistrationAPI
  participant token.decode
  participant createCredential
  participant CredentialsDB
  Client->>RegistrationAPI: Send WebAuthn registration request with Wa-Id
  RegistrationAPI->>token.decode: Decode token with chat secret and wa-link audience
  token.decode-->>RegistrationAPI: Return waId
  RegistrationAPI->>createCredential: Pass waId with credential data
  createCredential->>CredentialsDB: Insert credential and waId
  CredentialsDB-->>Client: Return registration response
Loading

Suggested reviewers: cruzdanilo, nfmelendez, cruzdanilo

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: linking a Wa-Id during server registration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch link
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch link

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the ability to link a WhatsApp ID (waId) to a user's credential during registration. It adds a unique waId column to the credentials database table, validates the encrypted Wa-Id header in the registration endpoints, and implements JWT token utilities for encoding and decoding the WhatsApp ID. The feedback suggests adding a unit test to verify that attempting to register a duplicate waId is properly rejected.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +857 to +859
});

it("rejects registration with a wa id token for another audience", async () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Add a unit test to verify that attempting to register a duplicate waId is rejected with a 400 Bad Request and the correct error code. Note that validation of injected dependencies (like parsing the address) can be omitted for trusted values in tests.

  });

  it("rejects registration if the wa id is already linked", async () => {
    const id1 = "creda1";
    const account1 = "0x1234567890123456789012345678901234567812";
    vi.spyOn(derive, "default").mockReturnValue(account1);
    const token = await encode("5491123456789", chatSecret, "wa-link");
    const response1 = await registrationAppClient.index.$post(
      { json: registrationWebauthnAssertion({ id: id1, rawId: id1 }) },
      { headers: { cookie: "session_id=test-session", "Wa-Id": token } },
    );
    expect(response1.status).toBe(200);

    await redis.set("test-session", "test-challenge");
    const id2 = "creda2";
    const account2 = "0x1234567890123456789012345678901234567813";
    vi.spyOn(derive, "default").mockReturnValue(account2);
    const response2 = await registrationAppClient.index.$post(
      { json: registrationWebauthnAssertion({ id: id2, rawId: id2 }) },
      { headers: { cookie: "session_id=test-session", "Wa-Id": token } },
    );

    expect(response2.status).toBe(400);
    await expect(response2.json()).resolves.toStrictEqual({ code: "wa already linked" });
  });

  it("rejects registration with a wa id token for another audience", async () => {
References
  1. In TypeScript tests, validation of injected dependencies (e.g., using parse(Address, ...) on an injected address) is not mandatory. It can be omitted for trusted values even if other tests include it for consistency.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 79eeb584-b2b5-4ed4-8af5-0d523fd12c66

📥 Commits

Reviewing files that changed from the base of the PR and between 76a9dc4 and 3c4cfe1.

📒 Files selected for processing (10)
  • .changeset/quick-otters-link.md
  • server/api/auth/registration.ts
  • server/database/schema.ts
  • server/script/openapi.ts
  • server/test/api/auth.test.ts
  • server/test/utils/token.test.ts
  • server/utils/chatSecret.ts
  • server/utils/createCredential.ts
  • server/utils/token.ts
  • server/vitest.config.mts

Comment on lines +1 to +3
if (!process.env.CHAT_IDENTITY_KEY) throw new Error("missing chat key");

export default process.env.CHAT_IDENTITY_KEY as string; // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract to a constant to avoid the type assertion and lint disable.

By assigning the environment variable to a local constant first, TypeScript will correctly narrow its type after the if check, eliminating the need for as string and the eslint-disable-line comment.

♻️ Proposed refactor
-if (!process.env.CHAT_IDENTITY_KEY) throw new Error("missing chat key");
-
-export default process.env.CHAT_IDENTITY_KEY as string; // eslint-disable-line `@typescript-eslint/no-unnecessary-type-assertion`
+const chatSecret = process.env.CHAT_IDENTITY_KEY;
+if (!chatSecret) throw new Error("missing chat key");
+
+export default chatSecret;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!process.env.CHAT_IDENTITY_KEY) throw new Error("missing chat key");
export default process.env.CHAT_IDENTITY_KEY as string; // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion
const chatSecret = process.env.CHAT_IDENTITY_KEY;
if (!chatSecret) throw new Error("missing chat key");
export default chatSecret;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant