jmwalletd.auth
jmwalletd.auth
JWT token authority for the wallet daemon.
Implements HS256-based JWT tokens compatible with the reference JoinMarket implementation. Two token types are managed:
- Access token: short-lived (30 min), used in Authorization / x-jm-authorization headers.
- Refresh token: longer-lived (4 hr), used only to obtain new token pairs.
Signing keys are regenerated on daemon start and on each wallet unlock/create/lock cycle, ensuring tokens from previous sessions are always invalidated.
Attributes
ACCESS_TOKEN_EXPIRY_SECONDS = 1800
module-attribute
LEEWAY_SECONDS = 10
module-attribute
REFRESH_ROTATION_GRACE_SECONDS = 120.0
module-attribute
REFRESH_TOKEN_EXPIRY_SECONDS = 14400
module-attribute
Classes
JMTokenAuthority
dataclass
Manages JWT signing keys and token issuance/verification.
Compatible with the reference implementation's auth semantics: - Access and refresh tokens use separate signing keys. - Refresh key is rotated on every token refresh. - All keys are regenerated on reset (wallet lock/unlock cycle).
Two deliberate extensions over the reference behaviour make the refresh flow robust against legitimate concurrency without weakening the lock-invalidates-everything guarantee:
- After a rotation, the previous refresh key remains acceptable for
:data:
REFRESH_ROTATION_GRACE_SECONDSso concurrent/retried refresh requests do not spuriously log the client out. - Re-issuing tokens for an already-unlocked wallet (a second client or a
repeated unlock call) can skip the rotation entirely via
issue(..., rotate_refresh=False)so it does not invalidate the refresh token held by the first client.
Source code in jmwalletd/src/jmwalletd/auth.py
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | |
Attributes
scope: str
property
Return the current scope string for token payloads.
Methods:
issue(wallet_name: str, *, rotate_refresh: bool = True) -> TokenPair
Issue a new access + refresh token pair for the given wallet.
By default the refresh signing key is rotated, invalidating any
previously issued refresh token (the previous key stays valid for
:data:REFRESH_ROTATION_GRACE_SECONDS to tolerate concurrent
refreshes). Pass rotate_refresh=False when re-issuing tokens for
an already-authenticated session (e.g. a repeated unlock of the same
wallet) so outstanding refresh tokens stay usable.
Source code in jmwalletd/src/jmwalletd/auth.py
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | |
reset() -> None
Regenerate all signing keys, invalidating all existing tokens.
Source code in jmwalletd/src/jmwalletd/auth.py
81 82 83 84 85 86 87 | |
verify_access(token: str, *, verify_exp: bool = True) -> dict[str, str]
Verify an access token and return the decoded payload.
Args: token: The raw JWT string. verify_exp: Whether to enforce expiration. Set to False for the token-refresh flow (the expired access token is still accepted).
Raises: jwt.InvalidTokenError: On any verification failure.
Source code in jmwalletd/src/jmwalletd/auth.py
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | |
verify_refresh(token: str) -> dict[str, str]
Verify a refresh token and return the decoded payload.
Tokens signed with the immediately-previous refresh key are accepted
within :data:REFRESH_ROTATION_GRACE_SECONDS of the last rotation,
so a concurrent second refresh (browser retry, second tab) does not
spuriously fail. reset() still invalidates everything at once.
Raises: jwt.InvalidTokenError: On any verification failure.
Source code in jmwalletd/src/jmwalletd/auth.py
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | |
TokenPair
dataclass
A pair of access + refresh tokens with metadata.
Source code in jmwalletd/src/jmwalletd/auth.py
34 35 36 37 38 39 40 41 42 | |