Skip to content

fix(schemas): accept expires_in alongside expires_at in OAuthTokenAuthentication (#1024) - #1782

Open
awesome-pro wants to merge 1 commit into
airweave-ai:mainfrom
awesome-pro:fix/issue-1024-accept-expires-in-oauth-response
Open

fix(schemas): accept expires_in alongside expires_at in OAuthTokenAuthentication (#1024)#1782
awesome-pro wants to merge 1 commit into
airweave-ai:mainfrom
awesome-pro:fix/issue-1024-accept-expires-in-oauth-response

Conversation

@awesome-pro

@awesome-pro awesome-pro commented May 12, 2026

Copy link
Copy Markdown

Summary

  • Adds expires_in: Optional[int] field to OAuthTokenAuthentication so callers can pass the standard OAuth RFC 6749 §5.1 response field directly
  • A mode="before" model validator converts expires_in → expires_at = now + timedelta(seconds=expires_in) when expires_at is not provided
  • Existing expires_at usage is fully backward-compatible — if both are supplied, expires_at wins
  • Adds unit tests covering conversion, precedence, expiry validation, and backward compat

Problem

OAuth providers return expires_in (seconds, per RFC 6749 §5.1), but OAuthTokenAuthentication only accepted expires_at (datetime). This forced every SDK/API caller to manually compute the absolute timestamp:

// workaround callers had to write:
const expiresAt = new Date(Date.now() + authFields.expires_in * 1000)
  .toISOString().replace('Z', '');

Changes

File Change
backend/airweave/schemas/source_connection.py Add expires_in field + coerce_expires_in_to_expires_at validator
backend/tests/unit/schemas/test_source_connection_schemas.py 6 unit tests covering all behaviours

Test plan

  • expires_in alone → expires_at computed correctly (within 2 s of expected)
  • expires_at + expires_inexpires_at not overwritten
  • Neither field → both remain None
  • Negative expires_in (already expired) → ValidationError
  • expires_at alone → unchanged (backward compat)
  • Blank access_token with expires_in → still rejected

Fixes #1024


Summary by cubic

Add expires_in support to OAuthTokenAuthentication and auto-convert it to expires_at so callers can pass standard OAuth responses directly. Keeps expires_at precedence for backward compatibility and removes client-side timestamp math. Fixes #1024.

  • New Features
    • Accepts expires_in (RFC 6749 §5.1) and converts to expires_at via a model_validator(mode="before") when expires_at is missing.
    • If both are provided, expires_at wins; negative expires_in is rejected.
    • Added unit tests for conversion, precedence, and invalid inputs.

Written for commit 63a3ba8. Summary will update on new commits.

…hentication

OAuth providers return expires_in (seconds) per RFC 6749 §5.1, but the
OAuthTokenAuthentication schema required expires_at (datetime), forcing
callers to compute the absolute expiry themselves.

Now both are accepted: when expires_in is supplied without expires_at,
a before-validator converts it to expires_at = now + timedelta(seconds=expires_in).
When expires_at is explicitly provided it is never overwritten.

Fixes airweave-ai#1024

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="backend/airweave/schemas/source_connection.py">

<violation number="1" location="backend/airweave/schemas/source_connection.py:104">
P1: Using raw `expires_in` in a `before` model validator can throw unwrapped exceptions before Pydantic validates/coerces the field, so bad input may become a server error instead of a schema validation error.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

return values
if values.get("expires_in") is not None and values.get("expires_at") is None:
values["expires_at"] = datetime.now(timezone.utc) + timedelta(
seconds=values["expires_in"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: Using raw expires_in in a before model validator can throw unwrapped exceptions before Pydantic validates/coerces the field, so bad input may become a server error instead of a schema validation error.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/airweave/schemas/source_connection.py, line 104:

<comment>Using raw `expires_in` in a `before` model validator can throw unwrapped exceptions before Pydantic validates/coerces the field, so bad input may become a server error instead of a schema validation error.</comment>

<file context>
@@ -80,6 +80,30 @@ class OAuthTokenAuthentication(BaseModel):
+            return values
+        if values.get("expires_in") is not None and values.get("expires_at") is None:
+            values["expires_at"] = datetime.now(timezone.utc) + timedelta(
+                seconds=values["expires_in"]
+            )
+        return values
</file context>

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.

expires_at vs expires_in (oauth response)

1 participant