
    {i                     ~   d Z ddlmZmZmZ ddlmZmZ ddlm	Z	 ddl
mZmZmZmZmZmZ ddlmZ ddlmZmZmZmZ ddlmZmZ  ed	d
g      Zej9                  deej:                  d      dede	defd       Zej9                  ded      dede	defd       Zej9                  ded      dede	defd       Z y)u  
認證相關 API:註冊 / 登入 / refresh token。

設計重點:
  1. email 一律存小寫,避免 "Robert@x.com" 跟 "robert@x.com" 被當成兩個帳號
  2. 第一個註冊的人自動成為 owner,後續都是 member
     (注意:Docker 啟動時 seed 已建立第一個 owner,實務上這條規則
      只在「跳過 seed、從零註冊」時才會觸發,但保留邏輯讓系統有自癒能力)
  3. 登入失敗的訊息一律「帳號或密碼錯誤」,不告訴攻擊者「這個 email 不存在」
    )	APIRouterHTTPExceptionstatus)funcselect)	DBSession)
TokenErrorcreate_access_tokencreate_refresh_tokendecode_tokenhash_passwordverify_password)User)LoginRequestLoginResponseRefreshRequest	TokenPair)
UserPublicUserRegisterRequestz	/api/authauth)prefixtagsz	/registeru   註冊新帳號)response_modelstatus_codesummarypayloaddbreturnc                   K   | j                   j                         }t        t              j	                  t        j                   |k(        }|j                  |       d{   j                         }|t        t        j                  d      t        t        j                               j                  t              }|j                  |       d{   j                         }|dk(  rdnd}t        |t        | j                        | j                   |d      }|j#                  |       |j%                          d{    |j'                  |       d{    |S 7 7 7 $7 w)	u   註冊新帳號。

    規則:
      - email 必須全域唯一(case-insensitive)
      - 第一個成功註冊的人自動成為 owner,其他都是 member
    Nu   此 email 已被註冊r   detailr   ownermemberT)emailpassword_hashdisplay_namerole	is_active)r$   lowerr   r   whereexecutescalar_one_or_noner   r   HTTP_409_CONFLICTr   countselect_from
scalar_oner   passwordr&   addcommitrefresh)	r   r   email_lowerexisting_stmtexisting
count_stmttotal_usersr'   users	            0/volume1/homes/robertsu/coba/app/app/api/auth.pyregisterr<       s+     --%%'K 4L&&tzz['@AMjj//CCEH00+
 	
 

%11$7JJ//;;=K!Q&7HD #G$4$45))D FF4L
))+
**T
K/ 0 0 sJ   AE* E!!A3E*E$A,E*E&E*E(E*$E*&E*(E*z/loginu   登入)r   r   c                   K   | j                   j                         }t        t              j	                  t        j                   |k(  t        j
                  j                  d            }|j                  |       d{   j                         }| t        | j                  |j                        st        t        j                  d      |j                  st        t        j                   d      t#        t%        t'        |j(                        t+        |j(                              t-        j.                  |            S 7 ǭw)u'   登入,取得 access + refresh token。Nu   帳號或密碼錯誤r    u"   帳號已停用,請聯絡管理員access_tokenrefresh_token)tokensr:   )r$   r)   r   r   r*   
deleted_atis_r+   r,   r   r1   r%   r   r   HTTP_401_UNAUTHORIZEDr(   HTTP_403_FORBIDDENr   r   r
   idr   r   model_validate)r   r   r5   stmtr:   s        r;   loginrI   K   s      --%%'K$<djjK79L9LT9RSD**T""668D |?7+;+;T=O=OP44*
 	

 >>117
 	

 ,TWW5.tww7
 &&t, ! #s   A<E>E?CEz/refreshu(   用 refresh token 換新的 access tokenc                 J  K   	 t        | j                  d      }t        t              j                  t        j                  |k(  t        j                  j                  d            }|j                  |       d{   j                         }||j                  st        t        j
                  d      t!        t#        |j                        t%        |j                              S # t        $ r*}t        t        j
                  t        |            |d}~ww xY w7 w)u   用 refresh token 換一組新的 access + refresh token。

    新拿一組 refresh token 是有意的(rotation):
    舊的 refresh token 應該由前端丟棄,降低被外洩重用的風險。
    r4   )expected_typer    Nu   使用者不存在或已停用r>   )r   r@   r	   r   r   rD   strr   r   r*   rF   rB   rC   r+   r,   r(   r   r
   r   )r   r   user_iderH   r:   s         r;   r4   r4   j   s     w44IN $<dgg0$//2E2Ed2KLD**T""668D|4>>443
 	

 (1*4773   44q6
 	 #s5   D#C+ A!D#<D!=A.D#+	D4%DDD#N)!__doc__fastapir   r   r   
sqlalchemyr   r   app.core.depsr   app.core.securityr	   r
   r   r   r   r   app.models.userr   app.schemas.authr   r   r   r   app.schemas.userr   r   routerpostHTTP_201_CREATEDr<   rI   r4        r;   <module>r\      s   	 5 4 # #  ! S S < 
+VH	5 ''	  "/ "Y "4 ""J XmXF 9   G< Z	;ef> y Y  gr[   