OAuth real browser login: GET callback, userinfo, API key issuance
Add GET /oauth/{provider}/callback (code+state from query, client_id
and redirect_uri recovered from the state record) alongside the
existing POST callback. Callback now fetches real userinfo
(GitHub api.github.com/user, Google openidconnect userinfo) and
issues a usable local API key on success: new users get the
create_user key, existing users rotate (old key dead, audit
oauth_login + api_key_rotated). Key plaintext appears only once
on the success page/response; failures are 400 HTML/JSON without
secrets, and a missing client_secret fails before any network call.
State remains one-time, 10 minutes, provider/redirect_uri matched.
Tests: 10 new OAuth real-login tests pass; host 27, cloud 249
(4 skipped are the pre-existing real-Postgres tests).
Real end-to-end still needs a registered OAuth App and a public
HTTPS callback entry (server still binds localhost only).
This commit is contained in:
1 parent
fbb00b8b6f
commit
0ed301b010
5 files changed
+637
-69
No files matched your search
@@ -84,6 +84,26 @@ class AuthStore:
|
||||
self.conn.commit()
|
||||
return user_id, api_key_plain
|
||||
|
||||
def rotate_api_key(self, user_id):
|
||||
"""轮换用户的 API key,返回新明文 key(只返回这一次)。
|
||||
|
||||
旧 key 的哈希即被替换,轮换后旧 key 立即失效。
|
||||
用户不存在时抛 ValueError(调用方不得用它凭空建用户)。
|
||||
"""
|
||||
row = self.conn.execute(
|
||||
"SELECT id FROM users WHERE id = ?", (user_id,)
|
||||
).fetchone()
|
||||
if row is None:
|
||||
raise ValueError("unknown user: %r" % (user_id,))
|
||||
api_key_plain = secrets.token_urlsafe(32)
|
||||
api_key_hash = hashlib.sha256(api_key_plain.encode()).hexdigest()
|
||||
self.conn.execute(
|
||||
"UPDATE users SET api_key_hash = ? WHERE id = ?",
|
||||
(api_key_hash, user_id),
|
||||
)
|
||||
self.conn.commit()
|
||||
return api_key_plain
|
||||
|
||||
def authenticate(self, api_key):
|
||||
"""用 API key 认证,成功返回 user_id,失败返回 None。"""
|
||||
if not api_key:
|
||||
|
||||
Reference in new issue
Block a user