From 905a056c87fd02547ffb24aa0d28540833c5f124 Mon Sep 17 00:00:00 2001 From: Kobe Date: Wed, 4 Jun 2025 14:21:12 +0200 Subject: [PATCH] Better password security for new users --- __pycache__/models.cpython-313.pyc | Bin 29344 -> 30144 bytes app.py | 2 + models.py | 56 ++++---- routes/__pycache__/auth.cpython-313.pyc | Bin 13624 -> 14393 bytes routes/__pycache__/contacts.cpython-313.pyc | Bin 24668 -> 24668 bytes routes/__pycache__/main.cpython-313.pyc | Bin 78708 -> 78726 bytes routes/auth.py | 23 ++- routes/main.py | 2 +- templates/auth/setup_password.html | 133 ++++++++++++++---- templates/settings/tabs/email_templates.html | 4 +- .../__pycache__/notification.cpython-313.pyc | Bin 14811 -> 14995 bytes utils/notification.py | 3 + 12 files changed, 166 insertions(+), 57 deletions(-) diff --git a/__pycache__/models.cpython-313.pyc b/__pycache__/models.cpython-313.pyc index 7cefa31d72d69ca6fda084a41f565addb65c7702..ca58e881759f4f853175b0c5091e973dc6eec0f3 100644 GIT binary patch delta 7791 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 delta 6921 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 Hh1CB7WE5!- diff --git a/app.py b/app.py index 48d597b..fcdde20 100644 --- a/app.py +++ b/app.py @@ -29,6 +29,8 @@ def create_app(): app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'your-secure-secret-key-here') app.config['UPLOAD_FOLDER'] = os.path.join(app.root_path, 'static', 'uploads') app.config['CSS_VERSION'] = os.getenv('CSS_VERSION', '1.0.3') # Add CSS version for cache busting + app.config['SERVER_NAME'] = os.getenv('SERVER_NAME', '127.0.0.1:5000') + app.config['PREFERRED_URL_SCHEME'] = os.getenv('PREFERRED_URL_SCHEME', 'http') # Initialize extensions db.init_app(app) diff --git a/models.py b/models.py index 202ee97..576a659 100644 --- a/models.py +++ b/models.py @@ -34,7 +34,11 @@ class User(UserMixin, db.Model): is_active = db.Column(db.Boolean, default=True) profile_picture = db.Column(db.String(255)) preferred_view = db.Column(db.String(10), default='grid', nullable=False) # 'grid' or 'list' - room_permissions = relationship('RoomMemberPermission', back_populates='user') + room_permissions = relationship( + 'RoomMemberPermission', + back_populates='user', + cascade='all, delete-orphan' + ) def set_password(self, password): self.password_hash = generate_password_hash(password) @@ -50,10 +54,10 @@ class Room(db.Model): name = db.Column(db.String(100), nullable=False) description = db.Column(db.Text) created_at = db.Column(db.DateTime, default=datetime.utcnow) - created_by = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + created_by = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False) # Relationships - creator = db.relationship('User', backref='created_rooms', foreign_keys=[created_by]) + creator = db.relationship('User', backref=db.backref('created_rooms', cascade='all, delete-orphan'), foreign_keys=[created_by]) members = db.relationship('User', secondary=room_members, backref=db.backref('rooms', lazy='dynamic')) member_permissions = relationship('RoomMemberPermission', back_populates='room', cascade='all, delete-orphan') files = db.relationship('RoomFile', back_populates='room', cascade='all, delete-orphan') @@ -65,7 +69,7 @@ class Room(db.Model): class RoomMemberPermission(db.Model): __tablename__ = 'room_member_permissions' room_id = db.Column(db.Integer, db.ForeignKey('room.id'), primary_key=True) - user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), primary_key=True) can_view = db.Column(db.Boolean, default=True, nullable=False) can_download = db.Column(db.Boolean, default=False, nullable=False) can_upload = db.Column(db.Boolean, default=False, nullable=False) @@ -86,13 +90,13 @@ class RoomFile(db.Model): type = db.Column(db.String(10), nullable=False) # 'file' or 'folder' size = db.Column(db.Integer) # in bytes, null for folders modified = db.Column(db.Float) # timestamp - uploaded_by = db.Column(db.Integer, db.ForeignKey('user.id')) + uploaded_by = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')) uploaded_at = db.Column(db.DateTime, default=datetime.utcnow) deleted = db.Column(db.Boolean, default=False) # New field for deleted status - deleted_by = db.Column(db.Integer, db.ForeignKey('user.id')) # New field for tracking who deleted the file + deleted_by = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')) deleted_at = db.Column(db.DateTime) # New field for tracking when the file was deleted - uploader = db.relationship('User', backref='uploaded_files', foreign_keys=[uploaded_by]) - deleter = db.relationship('User', backref='deleted_room_files', foreign_keys=[deleted_by]) + uploader = db.relationship('User', backref=db.backref('uploaded_files', cascade='all, delete-orphan'), foreign_keys=[uploaded_by]) + deleter = db.relationship('User', backref=db.backref('deleted_room_files', cascade='all, delete-orphan'), foreign_keys=[deleted_by]) room = db.relationship('Room', back_populates='files') starred_by = db.relationship('User', secondary='user_starred_file', backref='starred_files') @@ -102,7 +106,7 @@ class RoomFile(db.Model): class UserStarredFile(db.Model): __tablename__ = 'user_starred_file' id = db.Column(db.Integer, primary_key=True) - user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False) file_id = db.Column(db.Integer, db.ForeignKey('room_file.id'), nullable=False) starred_at = db.Column(db.DateTime, default=datetime.utcnow) @@ -123,13 +127,13 @@ class TrashedFile(db.Model): type = db.Column(db.String(10), nullable=False) # 'file' or 'folder' size = db.Column(db.Integer) # in bytes, null for folders modified = db.Column(db.Float) # timestamp - uploaded_by = db.Column(db.Integer, db.ForeignKey('user.id')) + uploaded_by = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE')) uploaded_at = db.Column(db.DateTime, default=datetime.utcnow) - deleted_by = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + deleted_by = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False) deleted_at = db.Column(db.DateTime, default=datetime.utcnow) room = db.relationship('Room', backref='trashed_files') - uploader = db.relationship('User', foreign_keys=[uploaded_by], backref='uploaded_trashed_files') - deleter = db.relationship('User', foreign_keys=[deleted_by], backref='deleted_trashed_files') # Changed from deleted_files to deleted_trashed_files + uploader = db.relationship('User', foreign_keys=[uploaded_by], backref=db.backref('uploaded_trashed_files', cascade='all, delete-orphan')) + deleter = db.relationship('User', foreign_keys=[deleted_by], backref=db.backref('deleted_trashed_files', cascade='all, delete-orphan')) def __repr__(self): return f'' @@ -197,10 +201,10 @@ class Conversation(db.Model): name = db.Column(db.String(100), nullable=False) description = db.Column(db.Text) created_at = db.Column(db.DateTime, default=datetime.utcnow) - created_by = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + created_by = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False) # Relationships - creator = db.relationship('User', backref='created_conversations', foreign_keys=[created_by]) + creator = db.relationship('User', backref=db.backref('created_conversations', cascade='all, delete-orphan'), foreign_keys=[created_by]) members = db.relationship('User', secondary=conversation_members, backref=db.backref('conversations', lazy='dynamic')) messages = db.relationship('Message', back_populates='conversation', cascade='all, delete-orphan') @@ -212,11 +216,11 @@ class Message(db.Model): content = db.Column(db.Text, nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) conversation_id = db.Column(db.Integer, db.ForeignKey('conversation.id'), nullable=False) - user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False) # Relationships conversation = db.relationship('Conversation', back_populates='messages') - user = db.relationship('User', backref='messages') + user = db.relationship('User', backref=db.backref('messages', cascade='all, delete-orphan')) attachments = db.relationship('MessageAttachment', back_populates='message', cascade='all, delete-orphan') def __repr__(self): @@ -284,14 +288,14 @@ class Event(db.Model): __tablename__ = 'events' id = db.Column(db.Integer, primary_key=True) event_type = db.Column(db.String(50), nullable=False) - user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), nullable=True) timestamp = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) details = db.Column(db.JSON) # Store additional event-specific data ip_address = db.Column(db.String(45)) # IPv6 addresses can be up to 45 chars user_agent = db.Column(db.String(255)) # Relationships - user = db.relationship('User', backref='events') + user = db.relationship('User', backref=db.backref('events', cascade='all, delete-orphan')) def __repr__(self): return f'' @@ -316,14 +320,14 @@ class Notif(db.Model): __tablename__ = 'notifs' id = db.Column(db.Integer, primary_key=True) notif_type = db.Column(db.String(50), nullable=False) - user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) - sender_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False) + sender_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), nullable=True) timestamp = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) read = db.Column(db.Boolean, default=False, nullable=False) details = db.Column(db.JSON) # Store additional notification-specific data # Relationships - user = db.relationship('User', foreign_keys=[user_id], backref='notifications') + user = db.relationship('User', foreign_keys=[user_id], backref=db.backref('notifications', cascade='all, delete-orphan')) sender = db.relationship('User', foreign_keys=[sender_id], backref='sent_notifications') def __repr__(self): @@ -337,11 +341,11 @@ class EmailTemplate(db.Model): body = db.Column(db.Text, nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) - created_by = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + created_by = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False) is_active = db.Column(db.Boolean, default=True) # Relationships - creator = db.relationship('User', backref='created_email_templates', foreign_keys=[created_by]) + creator = db.relationship('User', backref=db.backref('created_email_templates', cascade='all, delete-orphan'), foreign_keys=[created_by]) def __repr__(self): return f'' @@ -368,14 +372,14 @@ class Mail(db.Model): class PasswordSetupToken(db.Model): __tablename__ = 'password_setup_tokens' id = db.Column(db.Integer, primary_key=True) - user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False) token = db.Column(db.String(100), unique=True, nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) expires_at = db.Column(db.DateTime, nullable=False) used = db.Column(db.Boolean, default=False) # Relationships - user = db.relationship('User', backref='password_setup_tokens') + user = db.relationship('User', backref=db.backref('password_setup_tokens', cascade='all, delete-orphan')) def is_valid(self): return not self.used and datetime.utcnow() < self.expires_at diff --git a/routes/__pycache__/auth.cpython-313.pyc b/routes/__pycache__/auth.cpython-313.pyc index b653a18c9d173551c801195f74d49e7d15f49e3b..c24b9567b4ce5c1ad8dd71f45597592b4ee0f559 100644 GIT binary patch delta 2642 zcmaJ@U2Gdw7M^Q+JaOWmxQ*kG#P-B>(l|*I$0Q|5w`@Y&1X>!panqCrm>PSMm^!w5 z$8Jbg+H3_{T9BY{CB)B4mD+uvFG#yV6eJLfgjOs_G;RZ~MiEaH6}vB~QY|YU3g=uC z=SPB({rQ|T=iGDd`MK90Jo913zG}0T3-H`|pnvL2`0w@xgF^z=2jA5ygCIOe*gpd@ z`^jMu|0@XNCU(|bUp-<$lg-b#zFuv8r4);Q?qg|!>q=Z9dp~Gx$IOsNOBn^6Vucp zws3{$Vm5tBO|ex&U(JtITrV~iO--sL#h{Z78{PBN3HrKtI;YZE-7=9#X2I*zv$_F1 zn~SVou0ESdP*qc+Sz3ka=3F+GO3&!xM1pErDuRrf5wPgGYb_R~7#fd6qu30(JvODr zPOS$KH0h=&oz!&msTuz3n2Ku3nP@T*SGb){CNvXR%IvSkv-8)1`v0GWTNUm?MKgSE zc|!&7?!0$*!P}qr_Al22E;`SFm~)O&;2{ z@U^MT(7FSZDRwk%0tgb_noChN8dqZJTq>(!i-H3{y^i%tb<8h?iI0s+J)~>lWhrMO zdzs0)gAA}{>rTKUR@%D{wTT=QDUOA1;eD5iPOEePcshiDI#fo$XsHLm&wjG{$qweX zwK!=2C5;Fz2yJZA79>sVRoi{_e3e}&H3EPeE_et9``Ev1KGMm)vo(>X1)Kc}AzhcR zI>g$3e7hHcD<4COTkL1~D!=diODccq689dHDt*(Kb>(y;!d_i)5136##wq}SN!ej)ETT_6$ zr-$*CwdjHGc|9KPkSD7YJRN13T9>&CEj!suwaw%;_FioU{4~}P5n6DkypIgCQ*zkP z>w@`)x@6)|5+ysEQHxFYFxo%DK9K`vEI2gW_wua&DI^tylK@ArY^>QC6c4fI8v~hOJsOqKGK?5P|Kdo1L&VmLU5K=-*re{=2 zp97vBK^PBO{{`U%0FeN34!QFHtg@|<-D^vBuP+c!OeV6wv4CZL0d{fM1#*^!+Ux3m z-Jy+5w8zM^?2Go!J+tWdB!Y&3O{m*9-yn1jgwUA>QK2-+bN92B4sUo&O~%sGs+>*B z`_r-9kz7(!+lvjg^AE@BHgU1zbL&a zvBTk(R>@+s->nvGj#mzzKUlDO@>b81)pyq{*xVPgOV;{jt8;PN(C7CpStIO|nrch$ z4M*Q%Ss(3U?}tMJ@MlP%ClIC(rV;*#@H&7d0q_`pX%D?3kxFD0n$BfajlK?G z-)=ih#65UF4Uszrq+xM)*Bd>rfP!A8D0p!w3VjP$+KqsgG>K3=4}j9kem9+t=aTA! V^b)(>wa@M(-qoft6M@Cx_0Q?5V>18% delta 2199 zcmaJ?TWl0n7@o7cvzP96d#5e!c8Bf4cCjtpLNE40p+#CK6tFE~Xu1x&({^Z?*`AqA zQ-}e@2lT<9M+`(wjK&9|F)?d`CYT_JiI3HyH51eXN!0iPmIQ-|`u}I?g~l-1{pLUC zzn$~n=JV8N$Nh`Bxj6znU9lY#*SCD)-(brVVHt$~;->)H2}?yJHfr;bc2-{{O@@R) z(nNp*PfhkE$dVu&bv;}NgSJ6?lP$ZFgrlCN{iEI`YS6J{Vd)(fhHN2WSZEN2$u81N z!a`IWU|%}gPFt=SnQE#w{-9V+n-lek30WIg;wjlMW^|g2I%zJLwenRXj(kNk)r4#+ zNz0#@P!f~Nf)uYsod|f+sNHhNblk9U-Koh7E-pBmFwNB~Ewr z0pqVT!fkKqg0}{KAB`;3^?h5{_x1XPx}k6ChHm-_<_f#7?YZF_xLH*3cJExcZN9R7 zzNll)*TKqNfx>EZyAi;^xI8D(FzaZdqs6k`JbIJhb1oQs`3a?j=vLyLC7>A!>2 zogBeeHRlM>NaljeZQG2wp``$pO-)9L#qUBMoBD%dZcMpDAo z<+PGW=0Hx`L1OGHe>G`hzxyMA1$nfy1*M4;6)jg9?j2W?mWLCXd_tj3z@_a7C`T(0 zDiHz*RqSeBhy>Zsd7CRNaXPKgS<5q~PMKim*sLXvsnjs3#Mb9Wn`=SAa-(H5Ymb zq0$|&KAFpFhAQK#0SAf_I>5dt4LY+5>{e;Q$<>-ze_5cj2Q72~SYCen1cVi|x^c%n zqa2Hm>GVX@LAl&66y|nzBh|@1C<~tEsadWu>Oc->_acR5MDq~#AnZlxN5Imwe1>AK zRI4BN_u;->PA2JoT&_dF7}D&aktznToM|(m=`&W$qZ&FMQZ{J`at|P2e?Jn|l}H_A z)8%Cl+gHkK$)U`Z^6w$GnO7@%Nk3jXYF~TRPBgU>S-z|T?7dY-;{JG0md{W3GbvEw zjG$ncwFhd*IhG8hz|VI9B0@1PgnCFftEg@aEyoq&pGrdLUT1|I_%tm zWEHzqJsUcVYMbCT@^ z-T{;BRNX(2*OT?TZTAIsz5YtmDUbjimugJuGYX~8!hmF7&t`4;ZeW;tO1zyPrh|*CQq2r>@Q3 z@Ezixop<-mSH|Xx8s~hCxAUszf=|xpwavNPXoOv9YS>v1*67m+GQt>wiSQbLf!{># z`(Zg)UR6`gIMvgpV$e5%Tg+;je=6Kd#CH5N^^&_bq``Pu%f;50LBMjy`7n>i=>=F) n{JL>Xsvwn}09>x)gq}=KDck8g?6;O4zl&5Y77jTG7z6fy>TmxJ diff --git a/routes/__pycache__/contacts.cpython-313.pyc b/routes/__pycache__/contacts.cpython-313.pyc index 24cac0ca2a99ba79825d8672f1f2936214462d2d..33a378db36a8f2b1eb3f639be869a5dd21a132cc 100644 GIT binary patch delta 21 bcmca}fbq@&My}7iyj%=G5N@`SD<}Z~R1F4J delta 21 bcmca}fbq@&My}7iyj%=G5N^1UD<}Z~Q}hN= diff --git a/routes/__pycache__/main.cpython-313.pyc b/routes/__pycache__/main.cpython-313.pyc index fcba5ca355db065c052b65c08245f330fe79978b..3b7f8edf9b68a4d3be2b790ce4858d0a37559050 100644 GIT binary patch delta 5890 zcmb_gdvH|c704StZj!}hclquO8mO*; zPOIaHt)F$L{vk$fEtRo_RYRag+N!*&q9DliS!=OYMynM?3)=HLH`$x4P?>2nnP0x= zd7tl`n@^5-e*CT{|6lp}fh_uY_Ph(5@7bI`bBOpyYTl5`-R@iJf=cWwR6Bwr)K?b< z)ZSrFh+-w)D=e6pRW}P)@N;Eug|J6;#mOOOi$Yh0OMRe?Ry&5}rcMsOcY4X{$t4ZC z8H%(RQ@{P~DdAXjl`f5t8Hq*fBMnou%F0O8)SGmvem#GmSej~IaL^?}s%GIhVXD}| z*#S>DWSDXSF;R~#oGX^9j~7lHxs0arY68m}F`_Zka<}MFxm=Z9nwAH1&r1P}q#DikR*4Dm7W~;edV~L7|3jXAlNRp}UVflv>7klyUaYxhe?i@$g8CN=>QhH9`)Fw1 z^bu7}Wu7mmjjU>#k^1Ykmkbt1^&-DL<#ivtm8!!09`>8E!7I?)@b^MmX+1FxY37{5N+~wbDmSId6^K{mX2B~AZ zh+w0TJMqUQkE`mA1ryUkZS=`GID}mZ`&u5UhZ^KAwWXu!JJ8BZ zqh9sTwL#%c4PBS*PcXq_IKgK>OJ5jh82LFi(uR>=V460J{5Pg22sXNH4IFU)tfJ^`c0~qCD#2Q(cp4LS9bk4}7Bf-?j+n0JMa8}$o&c`wf zwf@c-GnfWD;B%i~gJ}U|7)4MkyJ@cF_H*-S>>=6u`I-f1 zNe5#CQptmWF{%6SdRr9p7NAyhF-49EEzpnbTJ@{DgW_On=iL)r69)>xH);n8p#sTL zUoVTw2ZA-qc&;Eg@4kXNQAY|G+s3iia#Y z99K9gIPBR839P}*mNy=1BDx4^%Wdc;yWDbz8X9c7azgOq!8yd)bSrLm07e1U0!9OF z2C&zkr;(-3e{g&AY{+mPK%+Hnusk2*8Q9MY$6Di|XnQ?M0Ba@L7HKocN}RRR4HvRu z6)I?k z-1ND7aX%@rx}1P4Y_v#-BbV{iY%%0jn0^-k!D=t*i%Q(r2K9ON;>~3uN5wWz7|HCJ zpIcsxYml9oKD_x}A4T3pTZ?lqz=~~nwQ}p#qE7AIT2(QFR*87pAsan*oyuXWMnkQ- z+zVZvBiQJYKf!pHs_HHk-KwQ~d4kQ7ja4te2Ur7m0gz#;VOX2%$bpe#GftLCI5G#o zA=85qE60|DhVm@}&0~w4%Rz*|A!RSyHo@a-0nzQs=((6A?CP1mtPUFXnG6Rrq&)+n zSO8}0;vgknbDq)yeY{reKBp91ET3Ugdli;jh>exs7sxHKCaS=qUbaHCsM@i+z;_Xb z^VQ+qGe%f>I8fgfZ=hsukTLA6R7HEvA^B_eTt~fFYR{AW#XT^n0opfGMp&uUqOvrrj#!K#g2G3erg+HP#pJU?`wV(PSCb0HS%=T?Ht2j_SP)^Z^q zM;QA@VT~INH%6!u>@%*ws9aVc60Z*> zsMP9)!L29d0t<>BfV%;$u-7NhG63)c{*TVdYXq-3pvuy5f5vr{J=TMbEjWTsfD$Vs z?9#aA{`8>-A9LX9rcN|0|Fq~o-ayg*_9F`1`TX>sa4qzVh0E) ze>e|8Q|?V3%ojl)+7!7md4h-}mSgOkcFAXH!OHIInqNo zS(EsigZ;RW^?&^w1DxsW_j>X#lSS3A zEFx-&snP}aJPi)dBrB$f;*rl{yc4jC;B?;4@U){9$ARN`HWNE}+rT*Jz_bLwFBm^s ze+E~68QHL`o2@s_y(8ulK8G{>JO(nopYc3$4n2?KO#DdK{1Wkmt6*UJZuy#YGkimX zJdixAOq3*k2Tp$n90hy`_!!^?p%H+wfFNKJpcJqK@O?l8AOUTFb$~|zvj8bT58wsB zVF1hdE=C^;>f7bJ$tTN1rTDb#c$wJY6)z>bXNn~vk<2X@lXLOX_nIwvLAe;^>&8Mj bSywJ9bKiuZYXF}n?=Pot_9uJF#S{MlhF4`1 delta 5844 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 diff --git a/routes/auth.py b/routes/auth.py index 566d03f..e07a853 100644 --- a/routes/auth.py +++ b/routes/auth.py @@ -11,9 +11,19 @@ auth_bp = Blueprint('auth', __name__) def require_password_change(f): @wraps(f) def decorated_function(*args, **kwargs): - if current_user.is_authenticated and current_user.check_password('changeme'): - flash('Please change your password before continuing.', 'warning') - return redirect(url_for('auth.change_password')) + if current_user.is_authenticated: + # Check if user has any valid password setup tokens + has_valid_token = PasswordSetupToken.query.filter_by( + user_id=current_user.id, + used=False + ).filter(PasswordSetupToken.expires_at > datetime.utcnow()).first() is not None + + if has_valid_token: + flash('Please set up your password before continuing.', 'warning') + return redirect(url_for('auth.setup_password', token=current_user.password_setup_tokens[0].token)) + elif current_user.check_password('changeme'): + flash('Please change your password before continuing.', 'warning') + return redirect(url_for('auth.change_password')) return f(*args, **kwargs) return decorated_function @@ -280,6 +290,7 @@ def init_routes(auth_bp): # Log password setup event log_event( event_type='user_update', + user_id=user.id, details={ 'user_id': user.id, 'user_name': f"{user.username} {user.last_name}", @@ -290,7 +301,9 @@ def init_routes(auth_bp): db.session.commit() - flash('Password set up successfully! You can now log in.', 'success') - return redirect(url_for('auth.login')) + # Log the user in and redirect to dashboard + login_user(user) + flash('Password set up successfully! Welcome to DocuPulse.', 'success') + return redirect(url_for('main.dashboard')) return render_template('auth/setup_password.html') \ No newline at end of file diff --git a/routes/main.py b/routes/main.py index 0a049f8..fc69fbf 100644 --- a/routes/main.py +++ b/routes/main.py @@ -70,7 +70,7 @@ def init_routes(main_bp): Event.user_id == current_user.id, # User's own actions db.and_( Event.event_type.in_(['conversation_create', 'message_create']), # Conversation-related events - Event.details['conversation_id'].cast(db.Integer).in_( + db.cast(text("(details->>'conversation_id')::integer"), db.Integer).in_( db.session.query(Conversation.id) .join(Conversation.members) .filter(User.id == current_user.id) diff --git a/templates/auth/setup_password.html b/templates/auth/setup_password.html index 1501c7a..a828382 100644 --- a/templates/auth/setup_password.html +++ b/templates/auth/setup_password.html @@ -8,28 +8,6 @@

Set Up Your Password

-
-
-
-
- -
-
-

Password Requirements

-
-
    -
  • At least 8 characters long
  • -
  • At least one uppercase letter
  • -
  • At least one lowercase letter
  • -
  • At least one number
  • -
  • At least one special character
  • -
-
-
-
-
-
-
@@ -50,12 +28,119 @@ class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2" style="--tw-ring-color: var(--primary-color);">
+ +
+

Password Requirements:

+
    +
  • + At least 8 characters long +
  • +
  • + At least one uppercase letter +
  • +
  • + At least one lowercase letter +
  • +
  • + At least one number +
  • +
  • + At least one special character +
  • +
+
- + +{% block extra_js %} + +{% endblock %} {% endblock %} \ No newline at end of file diff --git a/templates/settings/tabs/email_templates.html b/templates/settings/tabs/email_templates.html index 0fff081..3198615 100644 --- a/templates/settings/tabs/email_templates.html +++ b/templates/settings/tabs/email_templates.html @@ -78,7 +78,9 @@ const templateVariables = { 'user.position': 'The position of the user in their company', 'created_at': 'The date and time when the account was created', 'site.company_name': 'The name of your company', - 'site.company_website': 'Your company website URL' + 'site.company_website': 'Your company website URL', + 'setup_link': 'The link to set up the user\'s password (expires in 24 hours)', + 'created_by': 'The name of the admin who created the account' }, 'Password Reset': { 'user.username': 'The username of the account', diff --git a/utils/__pycache__/notification.cpython-313.pyc b/utils/__pycache__/notification.cpython-313.pyc index 4d2532f55dfd7a960bede6aa13569ff2440de138..c280b52985b3e8543f66586e2df5dc422eb4ad47 100644 GIT binary patch delta 1650 zcmbtUZD?Cn7(OSrH))gJbS@ulnx;wGwKmO~o3=^TcE(N7&Dasjy$$V_&62gr+BK&2 z7!h6e$lmFQf7xu-8SB+;<_urEAE)dHB@oP8t#IDr#=ohT zED1cQmz_i72&KBH(;!7z^1qkd62@f<4bfI=JBx*F7Msu_yTZD%HD`dO#zt`3x@^BA zWI`S!wSI;z@I}-Kx zR5`h$N~QBZR32t3b)B0kT@bK4IbT8m&vx%)-3cT;Uhc0Nr6;RTWiIy%8yX4Az3jXX^Y;o$q{(j5yiXI>y9ATZp^2Z!u&*bCO3OZm{yeeAyH(TD4sHG z9^vN}5g!D%>!|(+RUt|B71u$G1MppI3{QgJ{h`&ES5idDKR=^Pm-tLJm7gVt;48Ok zI>ZuKgXA#0?unVsP${ zVO1QtA&%VgbX*jch2q?mlQ%uFMgCS>4_s|`>7B?O+VH5i+yBA?qvkOIebeutxGEUO zb=+#BcPz@SMtRD~G_6@O6!K{@F*8-F%cpZw8YK&=rE|E(#{A?ggVz|GX0QPH?xlE! z(HaH~4E8e6GY}cP#DEP&Tnt(nxGDH*wUSkm3(R#6QvMrBfW^%=_dQ0@Ju7x-()_H z6sfgjnk*=DnaspgibgL%CgSHmrS>{_Cvv!Rkuo0_kB|Cv>wT2dr&woi=o86{bR!&t zJT&e4wqF{*GWGd0zi5189DW*DuVIP*0Q#w1nE(I) delta 1530 zcmbtSO>7%Q6rS;VH?Ey*qsC6K6aTE8q_rK#ZrmlLCA5fAk)#U9+szNIS~;=Z8eFH0 z?E@exjx7)X)$?|ZWg#(=25@I7hqP6S79xjm@Bt+{lN}O#G&RaD1h-cVau=$8?^ApWelasX$ zk*8~3%{L$v8sThfn_JNvRTKIkq#$Vi`ve33A=p_Hv}%EW1-sy_)^?#=Zvx*o<;m?j zN$fK;AArPD4{VK%wnL@0=zTVV@Oqol6iV)|=O+%pOAU6oZLx&@w@BC!>3KMjh#}JZ zP$J18IR1#*K3pGWW7P#Ms+$rAVJTwYojAC0P-j|fKq|<|(fU5|T%B#K2@NEtm+H{% z9HcuT?TkD7bRv_useZHkFOWNlLXu;!;UBvc)q=X#}RdLv#wh^nPR!v-woG0nhg*e#vHz-hQ0c5F^9?}eKXf!0wbNVyHqFI`T_o6>3C|4DZ zWlBa9CShh2A;w|R#L{ps77@?!&F`=p8>9R7Z1yIVPo`6)%qG2{ ziJgIXpDJG9rFY>(-w@aPx^L0*A>VVyn+6o8XUc^Vn}btvMf`-P`~{cdy>y{+Bi=~; z#znl39@b4)rF15r;cd}8`v9ogW&M=Xd;Bf#h3hdF468vdP*M-kvCBLfw6U$2yAPom zVckPj%I5N1t9G%M@NB)2(I6-=l&IRK%cYrY_NX!NdGPdy#m{+Mg3!l7z;{KQPUoX Optional[Mail]: if attr in notif.details[obj_name]: filled_body = filled_body.replace(f'{{{{ {key} }}}}', str(notif.details[obj_name][attr])) else: + # Special handling for setup_link to ensure it's a proper URL + if key == 'setup_link' and value.startswith('http://http//'): + value = value.replace('http://http//', 'http://') filled_body = filled_body.replace(f'{{{{ {key} }}}}', str(value)) # Handle special URL variables