§
    c¶»j7+  ã                  ó€   — d Z ddlmZ ddlZddlZddlZddlZddlmZm	Z	m
Z
mZ ddlmZmZmZmZ  G d„ d¦  «        ZdS )z«
Persistent SQLite database repository for FreeExile Accounts, Tokens, and Characters.
Implements ACID compliance, Write-Ahead Logging (WAL), and strict parameterization.
é    )ÚannotationsN)ÚAnyÚDictÚListÚOptional)ÚAccountÚAccountStatusÚOAuthIdentityÚOAuthProviderTypec                  óŽ   — e Zd ZdZd*d+d„Zd,d	„Zd-d
„Zd.d„Zej	        dfd/d„Z
d0d„Zd1d„Zd2d„Zd3d „Zd4d"„Zd5d6d'„Zd7d(„Zd8d)„ZdS )9ÚDatabaseAccountRepositoryz;Persistent SQLite Account, Token, and Character Repository.údata/freeexile_auth.dbÚdb_pathÚstrÚreturnÚNonec                ó<   — || _         |                      ¦   «          d S )N)r   Ú_init_database)Úselfr   s     ú-C:\Projects\FreeExile\server\auth\database.pyÚ__init__z"DatabaseAccountRepository.__init__   s!   € ØˆŒØ×ÒÑÔÐÐÐó    úsqlite3.Connectionc                óÜ   — t          j        | j        d¬¦  «        }t           j        |_        |                     d¦  «         |                     d¦  «         |                     d¦  «         |S )Ng      $@)ÚtimeoutzPRAGMA journal_mode = WAL;zPRAGMA foreign_keys = ON;zPRAGMA synchronous = NORMAL;)Úsqlite3Úconnectr   ÚRowÚrow_factoryÚexecute©r   Úconns     r   Ú_get_connectionz)DatabaseAccountRepository._get_connection   s`   € ÝŒ˜tœ|°TÐ:Ñ:Ô:ˆÝ"œ;ˆÔØ�ŠÐ1Ñ2Ô2Ð2Ø�ŠÐ0Ñ1Ô1Ð1Ø�ŠÐ3Ñ4Ô4Ð4Øˆr   c                óŠ   — |                       ¦   «         5 }|                     d¦  «         ddd¦  «         dS # 1 swxY w Y   dS )z/Create tables and indexes if they do not exist.ab  
                CREATE TABLE IF NOT EXISTS accounts (
                    account_id TEXT PRIMARY KEY,
                    email TEXT UNIQUE NOT NULL,
                    username TEXT UNIQUE NOT NULL,
                    password_hash TEXT NOT NULL,
                    status INTEGER NOT NULL DEFAULT 1,
                    roles TEXT NOT NULL DEFAULT '["player"]',
                    created_at REAL NOT NULL,
                    last_login_at REAL,
                    failed_login_attempts INTEGER NOT NULL DEFAULT 0,
                    lockout_until REAL
                );

                CREATE TABLE IF NOT EXISTS oauth_identities (
                    provider INTEGER NOT NULL,
                    provider_user_id TEXT NOT NULL,
                    account_id TEXT NOT NULL,
                    email TEXT NOT NULL,
                    linked_at REAL NOT NULL,
                    PRIMARY KEY (provider, provider_user_id),
                    FOREIGN KEY (account_id) REFERENCES accounts(account_id) ON DELETE CASCADE
                );

                CREATE TABLE IF NOT EXISTS refresh_tokens (
                    token_hash TEXT PRIMARY KEY,
                    account_id TEXT NOT NULL,
                    expires_at REAL NOT NULL,
                    is_revoked INTEGER NOT NULL DEFAULT 0,
                    created_at REAL NOT NULL,
                    FOREIGN KEY (account_id) REFERENCES accounts(account_id) ON DELETE CASCADE
                );

                CREATE TABLE IF NOT EXISTS remember_tokens (
                    token_hash TEXT PRIMARY KEY,
                    account_id TEXT NOT NULL,
                    device_id TEXT NOT NULL,
                    device_name TEXT NOT NULL,
                    hardware_hash TEXT NOT NULL,
                    client_ip TEXT NOT NULL,
                    expires_at REAL NOT NULL,
                    is_revoked INTEGER NOT NULL DEFAULT 0,
                    created_at REAL NOT NULL,
                    FOREIGN KEY (account_id) REFERENCES accounts(account_id) ON DELETE CASCADE
                );

                CREATE TABLE IF NOT EXISTS characters (
                    character_id TEXT PRIMARY KEY,
                    account_id TEXT NOT NULL,
                    name TEXT UNIQUE NOT NULL,
                    class_type TEXT NOT NULL,
                    level INTEGER NOT NULL DEFAULT 1,
                    season_id TEXT NOT NULL,
                    experience INTEGER NOT NULL DEFAULT 0,
                    current_hp INTEGER NOT NULL DEFAULT 100,
                    max_hp INTEGER NOT NULL DEFAULT 100,
                    current_mana INTEGER NOT NULL DEFAULT 50,
                    max_mana INTEGER NOT NULL DEFAULT 50,
                    current_zone_id TEXT NOT NULL DEFAULT 'zone_boundless_sanctuary',
                    pos_x REAL NOT NULL DEFAULT 0.0,
                    pos_y REAL NOT NULL DEFAULT 10.0,
                    inventory_json TEXT NOT NULL DEFAULT '[]',
                    created_at REAL NOT NULL,
                    FOREIGN KEY (account_id) REFERENCES accounts(account_id) ON DELETE CASCADE
                );

                CREATE TABLE IF NOT EXISTS security_audit_logs (
                    log_id TEXT PRIMARY KEY,
                    account_id TEXT,
                    event_type TEXT NOT NULL,
                    ip_address TEXT NOT NULL,
                    device_fingerprint TEXT,
                    status TEXT NOT NULL,
                    details TEXT,
                    timestamp REAL NOT NULL
                );

                CREATE INDEX IF NOT EXISTS idx_accounts_email ON accounts(email);
                CREATE INDEX IF NOT EXISTS idx_accounts_username ON accounts(username);
                CREATE INDEX IF NOT EXISTS idx_characters_account ON characters(account_id);
                CREATE INDEX IF NOT EXISTS idx_audit_account ON security_audit_logs(account_id);
            N)r#   Úexecutescriptr!   s     r   r   z(DatabaseAccountRepository._init_database$   s©   € à×!Ò!Ñ#Ô#ð R	 tØ×Òð Q ñ Qô Qð QðR	ð R	ð R	ñ R	ô R	ð R	ð R	ð R	ð R	ð R	ð R	ð R	øøøð R	ð R	ð R	ð R	ð R	ð R	s   •8¸<¿<Úrowúsqlite3.Rowr   c                óL  — |d         rt          j        |d         ¦  «        ndg}t          |d         |d         |d         |d         t          |d         ¦  «        ||d         |d	         |d
         |d         ¬¦
  «
        }|                      ¦   «         5 }|                     d|j        f¦  «        }|                     ¦   «         D ]D}t          |d         ¦  «        }t          ||d         |d         |d         ¬¦  «        |j
        |<   ŒE	 d d d ¦  «         n# 1 swxY w Y   |S )NÚrolesÚplayerÚ
account_idÚemailÚusernameÚpassword_hashÚstatusÚ
created_atÚlast_login_atÚfailed_login_attemptsÚlockout_until)
r+   r,   r-   r.   r/   r)   r0   r1   r2   r3   z^SELECT provider, provider_user_id, email, linked_at FROM oauth_identities WHERE account_id = ?ÚproviderÚprovider_user_idÚ	linked_at)r4   r5   r,   r6   )ÚjsonÚloadsr   r	   r#   r    r+   Úfetchallr   r
   Úoauth_identities)r   r&   r)   Úaccr"   ÚcurÚrÚp_types           r   Ú_row_to_accountz)DatabaseAccountRepository._row_to_accountz   s}  € Ø,/°¬LÐH•”
˜3˜wœ<Ñ(Ô(Ð(¸x¸jˆÝØ˜<Ô(Ø�g”,Ø˜”_Ø˜oÔ.Ý   X¤Ñ/Ô/ØØ˜<Ô(Ø˜oÔ.Ø"%Ð&=Ô">Ø˜oÔ.ð
ñ 
ô 
ˆð ×!Ò!Ñ#Ô#ð 	 tØ—,’,ØpØ”Ð!ñô ˆCð —\’\‘^”^ð ð �Ý*¨1¨Z¬=Ñ9Ô9�Ý/<Ø#Ø%&Ð'9Ô%:Ø˜Gœ*Ø œnð	0ñ 0ô 0�Ô$ VÑ,Ð,ðð	ð 	ð 	ñ 	ô 	ð 	ð 	ð 	ð 	ð 	ð 	øøøð 	ð 	ð 	ð 	ð ˆ
s   ÂA6DÄDÄ DNr,   r.   r-   r/   r	   r)   úOptional[list[str]]c                ó  — dt          j        ¦   «         j        d d…         › �}|                     ¦   «                              ¦   «         }|                     ¦   «         }|�|ndg}	t          j        |	¦  «        }
t          j        ¦   «         }|                      ¦   «         5 }| 	                    d|||||j
        |
|f¦  «         |                     ¦   «          d d d ¦  «         n# 1 swxY w Y   t          ||||||	|¬¦  «        S )NÚacc_é   r*   z«
                INSERT INTO accounts (account_id, email, username, password_hash, status, roles, created_at)
                VALUES (?, ?, ?, ?, ?, ?, ?)
                )r+   r,   r-   r.   r/   r)   r0   )ÚuuidÚuuid4ÚhexÚstripÚlowerr7   ÚdumpsÚtimer#   r    ÚvalueÚcommitr   )r   r,   r.   r-   r/   r)   r+   Ú
norm_emailÚ	norm_userÚ
roles_listÚ
roles_jsonÚnowr"   s                r   Úcreate_accountz(DatabaseAccountRepository.create_accountš   sI  € ð 4�DœJ™LœLÔ,¨S¨b¨SÔ1Ð3Ð3ˆ
Ø—[’[‘]”]×(Ò(Ñ*Ô*ˆ
Ø—N’NÑ$Ô$ˆ	Ø#Ð/�U�U°h°Zˆ
Ý”Z 
Ñ+Ô+ˆ
ÝŒi‰kŒkˆà×!Ò!Ñ#Ô#ð 	 tØ�LŠLðð ˜Z¨°MÀ6Ä<ÐQ[Ð]`Ðañô ð ð �KŠK‰MŒMˆMð	ð 	ð 	ñ 	ô 	ð 	ð 	ð 	ð 	ð 	ð 	øøøð 	ð 	ð 	ð 	õ Ø!ØØØ'ØØØð
ñ 
ô 
ð 	
s   Â 7C#Ã#C'Ã*C'r+   úOptional[Account]c                óü   — |                       ¦   «         5 }|                     d|f¦  «        }|                     ¦   «         }|r!|                      |¦  «        cd d d ¦  «         S 	 d d d ¦  «         n# 1 swxY w Y   d S )Nz+SELECT * FROM accounts WHERE account_id = ?)r#   r    Úfetchoner?   )r   r+   r"   r<   r&   s        r   Ú	get_by_idz#DatabaseAccountRepository.get_by_id½   sÖ   € Ø×!Ò!Ñ#Ô#ð 	1 tØ—,’,ÐLÈzÈmÑ\Ô\ˆCØ—,’,‘.”.ˆCØð 1Ø×+Ò+¨CÑ0Ô0ð		1ð 	1ð 	1ð 	1ñ 	1ô 	1ð 	1ð 	1ð1ð	1ð 	1ð 	1ñ 	1ô 	1ð 	1ð 	1ð 	1ð 	1ð 	1ð 	1øøøð 	1ð 	1ð 	1ð 	1ð
 ˆts   •AA1Á1A5Á8A5c                óH  — |                      ¦   «                              ¦   «         }|                      ¦   «         5 }|                     d|f¦  «        }|                     ¦   «         }|r!|                      |¦  «        cd d d ¦  «         S 	 d d d ¦  «         n# 1 swxY w Y   d S )Nz&SELECT * FROM accounts WHERE email = ?©rG   rH   r#   r    rU   r?   )r   r,   rM   r"   r<   r&   s         r   Úget_by_emailz&DatabaseAccountRepository.get_by_emailÅ   sï   € Ø—[’[‘]”]×(Ò(Ñ*Ô*ˆ
Ø×!Ò!Ñ#Ô#ð 	1 tØ—,’,ÐGÈ*ÈÑWÔWˆCØ—,’,‘.”.ˆCØð 1Ø×+Ò+¨CÑ0Ô0ð		1ð 	1ð 	1ð 	1ñ 	1ô 	1ð 	1ð 	1ð1ð	1ð 	1ð 	1ñ 	1ô 	1ð 	1ð 	1ð 	1ð 	1ð 	1ð 	1øøøð 	1ð 	1ð 	1ð 	1ð
 ˆtó   »ABÂBÂBc                óH  — |                      ¦   «                              ¦   «         }|                      ¦   «         5 }|                     d|f¦  «        }|                     ¦   «         }|r!|                      |¦  «        cd d d ¦  «         S 	 d d d ¦  «         n# 1 swxY w Y   d S )Nz0SELECT * FROM accounts WHERE LOWER(username) = ?rX   )r   r-   rN   r"   r<   r&   s         r   Úget_by_usernamez)DatabaseAccountRepository.get_by_usernameÎ   só   € Ø—N’NÑ$Ô$×*Ò*Ñ,Ô,ˆ	Ø×!Ò!Ñ#Ô#ð 	1 tØ—,’,ÐQÐT]ÐS_Ñ`Ô`ˆCØ—,’,‘.”.ˆCØð 1Ø×+Ò+¨CÑ0Ô0ð		1ð 	1ð 	1ð 	1ñ 	1ô 	1ð 	1ð 	1ð1ð	1ð 	1ð 	1ñ 	1ô 	1ð 	1ð 	1ð 	1ð 	1ð 	1ð 	1øøøð 	1ð 	1ð 	1ð 	1ð
 ˆtrZ   r4   r   r5   c                ó  — |                       ¦   «         5 }|                     d|j        |f¦  «        }|                     ¦   «         }|r'|                      |d         ¦  «        cd d d ¦  «         S 	 d d d ¦  «         n# 1 swxY w Y   d S )NzSSELECT account_id FROM oauth_identities WHERE provider = ? AND provider_user_id = ?r+   )r#   r    rK   rU   rV   )r   r4   r5   r"   r<   r&   s         r   Úget_by_oauthz&DatabaseAccountRepository.get_by_oauth×   sæ   € Ø×!Ò!Ñ#Ô#ð 	9 tØ—,’,ØeØ”Ð!1Ð2ñô ˆCð —,’,‘.”.ˆCØð 9Ø—~’~ c¨,Ô&7Ñ8Ô8ð	9ð 	9ð 	9ð 	9ñ 	9ô 	9ð 	9ð 	9ð9ð	9ð 	9ð 	9ñ 	9ô 	9ð 	9ð 	9ð 	9ð 	9ð 	9ð 	9øøøð 	9ð 	9ð 	9ð 	9ð ˆts   •AA=Á=BÂBÚaccountc           
     ó.  — t          j        |j        ¦  «        }|                      ¦   «         5 }|                     d|j        j        ||j        |j        |j	        |j
        f¦  «         |                     ¦   «          d d d ¦  «         d S # 1 swxY w Y   d S )NzÁ
                UPDATE accounts
                SET status = ?, roles = ?, last_login_at = ?, failed_login_attempts = ?, lockout_until = ?
                WHERE account_id = ?
                )r7   rI   r)   r#   r    r/   rK   r1   r2   r3   r+   rL   )r   r_   rP   r"   s       r   Úupdate_accountz(DatabaseAccountRepository.update_accountâ   sÚ   € Ý”Z ¤Ñ.Ô.ˆ
Ø×!Ò!Ñ#Ô#ð 	 tØ�LŠLðð ”NÔ(ØØÔ)ØÔ1ØÔ)ØÔ&ðñô ð ð �KŠK‰MŒMˆMð!	ð 	ð 	ñ 	ô 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	øøøð 	ð 	ð 	ð 	ð 	ð 	s   ®AB
Â
BÂBç     u"AÚrefresh_tokenÚttl_secondsÚfloatc                óì   — t          j         ¦   «         }||z   }|                      ¦   «         5 }|                     d||||f¦  «         |                     ¦   «          d d d ¦  «         d S # 1 swxY w Y   d S )Nz«
                INSERT OR REPLACE INTO refresh_tokens (token_hash, account_id, expires_at, is_revoked, created_at)
                VALUES (?, ?, ?, 0, ?)
                )rJ   r#   r    rL   )r   rc   r+   rd   rQ   Ú
expires_atr"   s          r   Ústore_refresh_tokenz-DatabaseAccountRepository.store_refresh_tokenö   sÂ   € ÝŒi‰kŒkˆØ˜;Ñ&ˆ
Ø×!Ò!Ñ#Ô#ð 	 tØ�LŠLðð  
¨J¸Ð<ñô ð ð �KŠK‰MŒMˆMð	ð 	ð 	ñ 	ô 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	øøøð 	ð 	ð 	ð 	ð 	ð 	s   ­/A)Á)A-Á0A-c                ó0  — t          j         ¦   «         }|                      ¦   «         5 }|                     d||f¦  «        }|                     ¦   «         }|r'|                      |d         ¦  «        cd d d ¦  «         S 	 d d d ¦  «         n# 1 swxY w Y   d S )Nz`SELECT account_id FROM refresh_tokens WHERE token_hash = ? AND is_revoked = 0 AND expires_at > ?r+   )rJ   r#   r    rU   rV   )r   rc   rQ   r"   r<   r&   s         r   Úget_account_by_refresh_tokenz6DatabaseAccountRepository.get_account_by_refresh_token  sî   € ÝŒi‰kŒkˆØ×!Ò!Ñ#Ô#ð 	9 tØ—,’,ØrØ Ð$ñô ˆCð —,’,‘.”.ˆCØð 9Ø—~’~ c¨,Ô&7Ñ8Ô8ð	9ð 	9ð 	9ð 	9ñ 	9ô 	9ð 	9ð 	9ð9ð	9ð 	9ð 	9ñ 	9ô 	9ð 	9ð 	9ð 	9ð 	9ð 	9ð 	9øøøð 	9ð 	9ð 	9ð 	9ð ˆts   ¨A	BÂBÂBc                ó¶   — |                       ¦   «         5 }|                     d|f¦  «         |                     ¦   «          d d d ¦  «         d S # 1 swxY w Y   d S )Nz=UPDATE refresh_tokens SET is_revoked = 1 WHERE token_hash = ?)r#   r    rL   )r   rc   r"   s      r   Úrevoke_refresh_tokenz.DatabaseAccountRepository.revoke_refresh_token  s›   € Ø×!Ò!Ñ#Ô#ð 	 tØ�LŠLÐXÐ[hÐZjÑkÔkÐkØ�KŠK‰MŒMˆMð	ð 	ð 	ñ 	ô 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	øøøð 	ð 	ð 	ð 	ð 	ð 	s   •,AÁAÁA)r   )r   r   r   r   )r   r   )r   r   )r&   r'   r   r   )r,   r   r.   r   r-   r   r/   r	   r)   r@   r   r   )r+   r   r   rS   )r,   r   r   rS   )r-   r   r   rS   )r4   r   r5   r   r   rS   )r_   r   r   r   )rb   )rc   r   r+   r   rd   re   r   r   )rc   r   r   rS   )rc   r   r   r   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__r   r#   r   r?   r	   ÚPENDING_ACTIVATIONrR   rV   rY   r\   r^   ra   rh   rj   rl   © r   r   r   r      s?  € € € € € ØEÐEðð ð ð ð ðð ð ð ðTð Tð Tð Tðlð ð ð ðJ !.Ô @Ø%)ð!
ð !
ð !
ð !
ð !
ðFð ð ð ðð ð ð ðð ð ð ð	ð 	ð 	ð 	ðð ð ð ð(ð ð ð ð ð
ð 
ð 
ð 
ðð ð ð ð ð r   r   )rp   Ú
__future__r   r7   r   rJ   rD   Útypingr   r   r   r   Úserver.auth.modelsr   r	   r
   r   r   rr   r   r   ú<module>rv      sã   ððð ð
 #Ð "Ð "Ð "Ð "Ð "Ø €€€Ø €€€Ø €€€Ø €€€Ø ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,ðð ð ð ð ð ð ð ð ð ð ð ð}ð }ð }ð }ð }ñ }ô }ð }ð }ð }r   