1"""Resource-server side: validate access tokens issued by this server.
2
3Kept separate from the authorization-server views so a resource server (e.g. a
4`plain.mcp` endpoint) can validate tokens without importing the view layer.
5"""
6
7from __future__ import annotations
8
9from typing import TYPE_CHECKING
10
11if TYPE_CHECKING:
12 from .models import AccessToken
13
14
15def validate_access_token(
16 token: str, *, resource: str | None = None
17) -> AccessToken | None:
18 """Return the live `AccessToken` for a bearer value, or `None`.
19
20 `None` covers unknown, expired, and revoked tokens. When `resource` is
21 given and the token is audience-bound (RFC 8707), the bound resource must
22 match — a token minted for a different endpoint is rejected. Omitting
23 `resource` (or a token with no bound resource) skips the audience check, so
24 pass `resource=` whenever the caller is a specific endpoint.
25 """
26 from .models import AccessToken, _hash_token
27
28 try:
29 access_token = AccessToken.query.select_related("user").get(
30 token_hash=_hash_token(token)
31 )
32 except AccessToken.DoesNotExist:
33 return None
34
35 if not access_token.is_valid():
36 return None
37 if resource and access_token.resource and access_token.resource != resource:
38 return None
39 return access_token