Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
A
aisbf
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
nexlab
aisbf
Commits
5ce4da23
Commit
5ce4da23
authored
May 11, 2026
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: remove stale self-signups after 14 days
parent
9ed0846b
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
169 additions
and
0 deletions
+169
-0
database.py
aisbf/database.py
+35
-0
auth.py
aisbf/routes/auth.py
+11
-0
main.py
main.py
+8
-0
test_auth_signup_cleanup.py
tests/routes/test_auth_signup_cleanup.py
+115
-0
No files found.
aisbf/database.py
View file @
5ce4da23
...
...
@@ -888,6 +888,41 @@ class DatabaseManager:
conn
.
commit
()
return
cursor
.
lastrowid
def
delete_stale_unverified_signup_users
(
self
,
inactivity_days
:
int
=
14
)
->
int
:
"""
Delete self-registered users who never logged in within the grace period.
Args:
inactivity_days: Number of days after registration before deletion.
Returns:
Number of deleted users.
"""
with
self
.
_get_connection
()
as
conn
:
cursor
=
conn
.
cursor
()
placeholder
=
'?'
if
self
.
db_type
==
'sqlite'
else
'
%
s'
if
self
.
db_type
==
'sqlite'
:
cutoff_expr
=
f
"datetime('now', '-' || {placeholder} || ' days')"
else
:
cutoff_expr
=
f
"DATE_SUB(NOW(), INTERVAL {placeholder} DAY)"
cursor
.
execute
(
f
'''
SELECT id
FROM users
WHERE role = 'user'
AND created_by IS NULL
AND last_login IS NULL
AND email_verified = 0
AND created_at <= {cutoff_expr}
'''
,
(
inactivity_days
,))
user_ids
=
[
row
[
0
]
for
row
in
cursor
.
fetchall
()]
for
user_id
in
user_ids
:
self
.
delete_user
(
user_id
)
return
len
(
user_ids
)
def
get_user_by_email
(
self
,
email
:
str
)
->
Optional
[
Dict
]:
"""
Get a user by email address.
...
...
aisbf/routes/auth.py
View file @
5ce4da23
...
...
@@ -31,6 +31,15 @@ def init(config, templates, server_config):
logger
=
logging
.
getLogger
(
__name__
)
def
cleanup_stale_signup_users
()
->
int
:
"""Delete self-registered users who never logged in within 14 days."""
db
=
DatabaseRegistry
.
get_config_database
()
deleted_count
=
db
.
delete_stale_unverified_signup_users
(
inactivity_days
=
14
)
if
deleted_count
:
logger
.
info
(
f
"Deleted {deleted_count} stale self-registered user(s) with no login activity"
)
return
deleted_count
@
router
.
get
(
"/dashboard/profile-pic"
)
async
def
dashboard_profile_pic
(
request
:
Request
):
"""Serve the logged-in user's profile picture from the database."""
...
...
@@ -130,6 +139,8 @@ async def dashboard_login(request: Request, username: str = Form(...), password:
"""Handle dashboard login"""
client_ip
=
request
.
client
.
host
if
request
.
client
else
"unknown"
cleanup_stale_signup_users
()
if
_login_rate_limit_check
(
client_ip
,
username
):
return
RedirectResponse
(
url
=
url_for
(
request
,
"/dashboard/login"
)
+
"?error=Too+many+failed+attempts.+Please+wait+and+try+again."
,
...
...
main.py
View file @
5ce4da23
...
...
@@ -349,6 +349,14 @@ async def _run_startup() -> None:
# Initialize routers with config/templates
_init_all_routers
()
try
:
from
aisbf.routes.auth
import
cleanup_stale_signup_users
deleted_stale_users
=
cleanup_stale_signup_users
()
if
deleted_stale_users
:
logger
.
info
(
f
"Startup cleanup removed {deleted_stale_users} stale self-registered user(s)"
)
except
Exception
as
e
:
logger
.
error
(
f
"Failed to clean up stale self-registered users: {e}"
)
# Background tasks
from
aisbf.app.model_cache
import
prefetch_global_provider_models
,
refresh_model_cache
asyncio
.
create_task
(
prefetch_global_provider_models
(
config
))
...
...
tests/routes/test_auth_signup_cleanup.py
0 → 100644
View file @
5ce4da23
from
datetime
import
datetime
,
timedelta
import
sys
from
pathlib
import
Path
from
uuid
import
uuid4
from
fastapi.testclient
import
TestClient
from
itsdangerous
import
TimestampSigner
from
base64
import
b64encode
import
json
sys
.
path
.
insert
(
0
,
str
(
Path
(
__file__
)
.
resolve
()
.
parents
[
2
]))
from
aisbf.database
import
DatabaseRegistry
from
main
import
app
def
_find_session_secret
()
->
str
:
for
middleware
in
app
.
user_middleware
:
kwargs
=
getattr
(
middleware
,
"kwargs"
,
{})
secret_key
=
kwargs
.
get
(
"secret_key"
)
if
secret_key
:
return
secret_key
raise
AssertionError
(
"Session middleware secret key not found"
)
def
_set_session_cookie
(
client
:
TestClient
,
data
:
dict
)
->
None
:
signer
=
TimestampSigner
(
_find_session_secret
())
serialized
=
b64encode
(
json
.
dumps
(
data
)
.
encode
(
"utf-8"
))
signed
=
signer
.
sign
(
serialized
)
.
decode
(
"utf-8"
)
client
.
cookies
.
set
(
"session"
,
signed
)
def
_login_as_admin
(
client
:
TestClient
)
->
None
:
_set_session_cookie
(
client
,
{
"logged_in"
:
True
,
"username"
:
"admin"
,
"role"
:
"admin"
,
"user_id"
:
None
,
"expires_at"
:
4102444800
,
},
)
def
test_login_cleanup_removes_self_registered_users_without_login_after_14_days
():
client
=
TestClient
(
app
)
db
=
DatabaseRegistry
.
get_config_database
()
token
=
uuid4
()
.
hex
stale_user_id
=
db
.
create_user
(
username
=
f
"stale-signup-{token}"
,
email
=
f
"stale-signup-{token}@example.com"
,
password_hash
=
"hash"
,
role
=
"user"
,
email_verified
=
False
,
)
active_user_id
=
db
.
create_user
(
username
=
f
"active-signup-{token}"
,
email
=
f
"active-signup-{token}@example.com"
,
password_hash
=
"hash"
,
role
=
"user"
,
email_verified
=
False
,
)
with
db
.
_get_connection
()
as
conn
:
cursor
=
conn
.
cursor
()
placeholder
=
'?'
if
db
.
db_type
==
'sqlite'
else
'
%
s'
cursor
.
execute
(
f
"UPDATE users SET created_at = {placeholder}, last_login = NULL, email_verified = 0, created_by = NULL WHERE id = {placeholder}"
,
((
datetime
.
now
()
-
timedelta
(
days
=
15
))
.
isoformat
(
sep
=
" "
),
stale_user_id
),
)
cursor
.
execute
(
f
"UPDATE users SET created_at = {placeholder}, last_login = NULL, email_verified = 0, created_by = NULL WHERE id = {placeholder}"
,
((
datetime
.
now
()
-
timedelta
(
days
=
13
))
.
isoformat
(
sep
=
" "
),
active_user_id
),
)
conn
.
commit
()
response
=
client
.
post
(
"/dashboard/login"
,
data
=
{
"username"
:
"definitely-not-a-user"
,
"password"
:
"wrong-password"
},
follow_redirects
=
False
,
)
assert
response
.
status_code
==
303
assert
db
.
get_user_by_id
(
stale_user_id
)
is
None
assert
db
.
get_user_by_id
(
active_user_id
)
is
not
None
def
test_admin_created_users_are_not_removed_by_signup_cleanup
():
db
=
DatabaseRegistry
.
get_config_database
()
token
=
uuid4
()
.
hex
invited_user_id
=
db
.
create_user
(
username
=
f
"invited-stale-{token}"
,
email
=
f
"invited-stale-{token}@example.com"
,
password_hash
=
"hash"
,
role
=
"user"
,
created_by
=
"admin"
,
email_verified
=
False
,
)
with
db
.
_get_connection
()
as
conn
:
cursor
=
conn
.
cursor
()
placeholder
=
'?'
if
db
.
db_type
==
'sqlite'
else
'
%
s'
cursor
.
execute
(
f
"UPDATE users SET created_at = {placeholder}, last_login = NULL, email_verified = 0, created_by = {placeholder} WHERE id = {placeholder}"
,
((
datetime
.
now
()
-
timedelta
(
days
=
30
))
.
isoformat
(
sep
=
" "
),
"admin"
,
invited_user_id
),
)
conn
.
commit
()
response
=
db
.
delete_stale_unverified_signup_users
(
inactivity_days
=
14
)
assert
response
==
0
assert
db
.
get_user_by_id
(
invited_user_id
)
is
not
None
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment