Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
S
Son Of Anton
Project
Project
Details
Activity
Releases
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
Administrator
Son Of Anton
Commits
91ada25a
Commit
91ada25a
authored
Apr 10, 2026
by
Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update 6 files via Son of Anton
parent
3c51ab07
Changes
6
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
182 additions
and
433 deletions
+182
-433
main.py
backend/main.py
+18
-1
models.py
backend/models.py
+12
-3
admin_routes.py
backend/routes/admin_routes.py
+26
-230
auth_routes.py
backend/routes/auth_routes.py
+19
-3
api.js
frontend/src/api.js
+7
-118
LoginPage.jsx
frontend/src/pages/LoginPage.jsx
+100
-78
No files found.
backend/main.py
View file @
91ada25a
...
@@ -48,12 +48,29 @@ def _run_migrations():
...
@@ -48,12 +48,29 @@ def _run_migrations():
from
backend.models
import
ChatAttachment
from
backend.models
import
ChatAttachment
ChatAttachment
.
__table__
.
create
(
bind
=
engine
,
checkfirst
=
True
)
ChatAttachment
.
__table__
.
create
(
bind
=
engine
,
checkfirst
=
True
)
# Create user_permissions table if missing
if
"user_permissions"
not
in
existing_tables
:
if
"user_permissions"
not
in
existing_tables
:
from
backend.models
import
UserPermissions
from
backend.models
import
UserPermissions
UserPermissions
.
__table__
.
create
(
bind
=
engine
,
checkfirst
=
True
)
UserPermissions
.
__table__
.
create
(
bind
=
engine
,
checkfirst
=
True
)
print
(
" Created user_permissions table"
)
print
(
" Created user_permissions table"
)
# ── App Settings table ──
if
"app_settings"
not
in
existing_tables
:
from
backend.models
import
AppSettings
AppSettings
.
__table__
.
create
(
bind
=
engine
,
checkfirst
=
True
)
print
(
" Created app_settings table"
)
# Ensure default app_settings row exists
from
backend.models
import
AppSettings
from
backend.database
import
SessionLocal
_db
=
SessionLocal
()
try
:
if
not
_db
.
query
(
AppSettings
)
.
first
():
_db
.
add
(
AppSettings
(
allow_registration
=
True
))
_db
.
commit
()
print
(
" Created default app_settings row"
)
finally
:
_db
.
close
()
for
table_name
in
[
"gitlab_settings"
,
"linked_repos"
,
"pending_actions"
]:
for
table_name
in
[
"gitlab_settings"
,
"linked_repos"
,
"pending_actions"
]:
if
table_name
not
in
existing_tables
:
if
table_name
not
in
existing_tables
:
print
(
f
" Creating {table_name} table"
)
print
(
f
" Creating {table_name} table"
)
...
...
backend/models.py
View file @
91ada25a
...
@@ -54,7 +54,6 @@ class UserPermissions(Base):
...
@@ -54,7 +54,6 @@ class UserPermissions(Base):
unique
=
True
,
nullable
=
False
,
index
=
True
,
unique
=
True
,
nullable
=
False
,
index
=
True
,
)
)
# Feature access
can_use_web_search
=
Column
(
Boolean
,
default
=
False
)
can_use_web_search
=
Column
(
Boolean
,
default
=
False
)
can_use_ui_design
=
Column
(
Boolean
,
default
=
False
)
can_use_ui_design
=
Column
(
Boolean
,
default
=
False
)
can_use_knowledge_base
=
Column
(
Boolean
,
default
=
True
)
can_use_knowledge_base
=
Column
(
Boolean
,
default
=
True
)
...
@@ -63,10 +62,8 @@ class UserPermissions(Base):
...
@@ -63,10 +62,8 @@ class UserPermissions(Base):
can_export_pptx
=
Column
(
Boolean
,
default
=
True
)
can_export_pptx
=
Column
(
Boolean
,
default
=
True
)
can_export_docx
=
Column
(
Boolean
,
default
=
True
)
can_export_docx
=
Column
(
Boolean
,
default
=
True
)
# Model access — "all" or comma-separated model IDs
allowed_models
=
Column
(
Text
,
default
=
"eu.anthropic.claude-haiku-4-5-20251001-v1:0"
)
allowed_models
=
Column
(
Text
,
default
=
"eu.anthropic.claude-haiku-4-5-20251001-v1:0"
)
# Limits (0 = unlimited for count-based limits)
max_tokens_cap
=
Column
(
Integer
,
default
=
4096
)
max_tokens_cap
=
Column
(
Integer
,
default
=
4096
)
max_reasoning_budget
=
Column
(
Integer
,
default
=
0
)
max_reasoning_budget
=
Column
(
Integer
,
default
=
0
)
max_chats
=
Column
(
Integer
,
default
=
50
)
max_chats
=
Column
(
Integer
,
default
=
50
)
...
@@ -81,6 +78,18 @@ class UserPermissions(Base):
...
@@ -81,6 +78,18 @@ class UserPermissions(Base):
user
=
relationship
(
"User"
,
back_populates
=
"permissions"
)
user
=
relationship
(
"User"
,
back_populates
=
"permissions"
)
# ═══════════════════════════════════════════════════════════
# App-wide Settings (singleton row)
# ═══════════════════════════════════════════════════════════
class
AppSettings
(
Base
):
__tablename__
=
"app_settings"
id
=
Column
(
String
(
36
),
primary_key
=
True
,
default
=
new_id
)
allow_registration
=
Column
(
Boolean
,
default
=
True
)
updated_at
=
Column
(
DateTime
,
default
=
datetime
.
utcnow
,
onupdate
=
datetime
.
utcnow
)
class
Chat
(
Base
):
class
Chat
(
Base
):
__tablename__
=
"chats"
__tablename__
=
"chats"
...
...
backend/routes/admin_routes.py
View file @
91ada25a
This diff is collapsed.
Click to expand it.
backend/routes/auth_routes.py
View file @
91ada25a
"""
"""
Authentication routes: register, login, profile — with permissions.
Authentication routes: register, login, profile — with permissions
+ registration toggle
.
"""
"""
from
pydantic
import
BaseModel
from
pydantic
import
BaseModel
...
@@ -7,7 +7,7 @@ from fastapi import APIRouter, Depends, HTTPException, status
...
@@ -7,7 +7,7 @@ from fastapi import APIRouter, Depends, HTTPException, status
from
sqlalchemy.orm
import
Session
from
sqlalchemy.orm
import
Session
from
backend.database
import
get_db
from
backend.database
import
get_db
from
backend.models
import
User
from
backend.models
import
User
,
AppSettings
from
backend.auth
import
(
from
backend.auth
import
(
hash_password
,
verify_password
,
create_token
,
get_current_user
,
hash_password
,
verify_password
,
create_token
,
get_current_user
,
get_user_permissions
,
ensure_user_permissions
,
get_user_permissions
,
ensure_user_permissions
,
...
@@ -28,8 +28,25 @@ class LoginBody(BaseModel):
...
@@ -28,8 +28,25 @@ class LoginBody(BaseModel):
password
:
str
password
:
str
@
router
.
get
(
"/config"
)
def
auth_config
(
db
:
Session
=
Depends
(
get_db
)):
"""Public endpoint — no auth needed. Returns registration status."""
settings
=
db
.
query
(
AppSettings
)
.
first
()
return
{
"allow_registration"
:
settings
.
allow_registration
if
settings
else
True
,
}
@
router
.
post
(
"/register"
)
@
router
.
post
(
"/register"
)
def
register
(
body
:
RegisterBody
,
db
:
Session
=
Depends
(
get_db
)):
def
register
(
body
:
RegisterBody
,
db
:
Session
=
Depends
(
get_db
)):
# Check if registration is enabled
app_settings
=
db
.
query
(
AppSettings
)
.
first
()
if
app_settings
and
not
app_settings
.
allow_registration
:
raise
HTTPException
(
status
.
HTTP_403_FORBIDDEN
,
"Registration is currently disabled. Contact an administrator."
,
)
if
db
.
query
(
User
)
.
filter
(
if
db
.
query
(
User
)
.
filter
(
(
User
.
username
==
body
.
username
)
|
(
User
.
email
==
body
.
email
)
(
User
.
username
==
body
.
username
)
|
(
User
.
email
==
body
.
email
)
)
.
first
():
)
.
first
():
...
@@ -46,7 +63,6 @@ def register(body: RegisterBody, db: Session = Depends(get_db)):
...
@@ -46,7 +63,6 @@ def register(body: RegisterBody, db: Session = Depends(get_db)):
db
.
commit
()
db
.
commit
()
db
.
refresh
(
user
)
db
.
refresh
(
user
)
# Auto-create permissions from defaults template
ensure_user_permissions
(
user
.
id
,
db
)
ensure_user_permissions
(
user
.
id
,
db
)
token
=
create_token
(
user
.
id
,
user
.
role
)
token
=
create_token
(
user
.
id
,
user
.
role
)
...
...
frontend/src/api.js
View file @
91ada25a
This diff is collapsed.
Click to expand it.
frontend/src/pages/LoginPage.jsx
View file @
91ada25a
import
React
,
{
useState
}
from
"react"
;
import
React
,
{
useState
,
useEffect
}
from
"react"
;
import
{
useApp
}
from
"../store"
;
import
{
useApp
}
from
"../store"
;
import
{
login
,
register
}
from
"../api"
;
import
{
login
,
register
,
getAuthConfig
}
from
"../api"
;
import
{
Flame
,
Eye
,
EyeOff
,
Loader2
}
from
"lucide-react"
;
import
{
Flame
,
LogIn
,
UserPlus
,
AlertCircle
}
from
"lucide-react"
;
export
default
function
LoginPage
()
{
export
default
function
LoginPage
()
{
const
{
dispatch
}
=
useApp
();
const
{
dispatch
}
=
useApp
();
...
@@ -9,114 +9,136 @@ export default function LoginPage() {
...
@@ -9,114 +9,136 @@ export default function LoginPage() {
const
[
username
,
setUsername
]
=
useState
(
""
);
const
[
username
,
setUsername
]
=
useState
(
""
);
const
[
email
,
setEmail
]
=
useState
(
""
);
const
[
email
,
setEmail
]
=
useState
(
""
);
const
[
password
,
setPassword
]
=
useState
(
""
);
const
[
password
,
setPassword
]
=
useState
(
""
);
const
[
showPw
,
setShowPw
]
=
useState
(
false
);
const
[
error
,
setError
]
=
useState
(
""
);
const
[
error
,
setError
]
=
useState
(
""
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
allowRegistration
,
setAllowRegistration
]
=
useState
(
true
);
const
[
configLoaded
,
setConfigLoaded
]
=
useState
(
false
);
useEffect
(()
=>
{
getAuthConfig
()
.
then
((
data
)
=>
{
setAllowRegistration
(
data
.
allow_registration
!==
false
);
setConfigLoaded
(
true
);
})
.
catch
(()
=>
{
setAllowRegistration
(
true
);
setConfigLoaded
(
true
);
});
},
[]);
async
function
handleSubmit
(
e
)
{
async
function
handleSubmit
(
e
)
{
e
.
preventDefault
();
e
.
preventDefault
();
setError
(
""
);
setError
(
""
);
setLoading
(
true
);
setLoading
(
true
);
try
{
try
{
const
res
=
isRegister
let
result
;
?
await
register
(
username
,
email
,
password
)
if
(
isRegister
)
{
:
await
login
(
username
,
password
);
if
(
!
allowRegistration
)
{
dispatch
({
type
:
"LOGIN"
,
token
:
res
.
token
,
user
:
res
.
user
});
setError
(
"Registration is disabled."
);
setLoading
(
false
);
return
;
}
result
=
await
register
(
username
,
email
,
password
);
}
else
{
result
=
await
login
(
username
,
password
);
}
dispatch
({
type
:
"LOGIN"
,
token
:
result
.
token
,
user
:
result
.
user
});
}
catch
(
err
)
{
}
catch
(
err
)
{
setError
(
err
.
message
||
"Authentication failed"
);
setError
(
err
.
message
||
"Something went wrong"
);
}
finally
{
setLoading
(
false
);
}
}
setLoading
(
false
);
}
}
return
(
return
(
<
div
className=
"h-
full h-dvh flex items-center justify-center bg-anton-bg px-4 safe-top safe-bottom
"
>
<
div
className=
"h-
dvh flex items-center justify-center bg-anton-bg p-4
"
>
<
div
className=
"w-full max-w-sm"
>
<
div
className=
"w-full max-w-sm"
>
{
/* Logo */
}
<
div
className=
"flex flex-col items-center mb-8"
>
<
div
className=
"text-center mb-8"
>
<
div
className=
"w-16 h-16 rounded-2xl bg-gradient-to-br from-anton-accent to-red-600 flex items-center justify-center shadow-lg shadow-anton-accent/20 mb-4"
>
<
div
className=
"w-16 h-16 mx-auto mb-4 rounded-2xl bg-gradient-to-br from-anton-accent to-red-600 flex items-center justify-center shadow-lg shadow-anton-accent/20"
>
<
Flame
size=
{
32
}
className=
"text-white"
/>
<
Flame
size=
{
32
}
className=
"text-white"
/>
</
div
>
</
div
>
<
h1
className=
"text-2xl font-bold text-white"
>
Son of Anton
</
h1
>
<
h1
className=
"text-2xl font-bold text-white"
>
Son of Anton
</
h1
>
<
p
className=
"text-anton-muted text-sm mt-1"
>
Avatar of All Elements of Code
</
p
>
<
p
className=
"text-anton-muted text-sm mt-1"
>
Avatar of All Elements of Code
</
p
>
</
div
>
</
div
>
{
/* Form */
}
<
div
className=
"bg-anton-card border border-anton-border rounded-2xl p-6"
>
<
form
onSubmit=
{
handleSubmit
}
className=
"space-y-4"
>
{
/* Tab buttons — only show Register tab if registration is allowed */
}
<
div
>
<
div
className=
"flex gap-2 mb-6"
>
<
label
className=
"text-xs text-anton-muted mb-1.5 block"
>
Username
</
label
>
<
button
<
input
onClick=
{
()
=>
{
setIsRegister
(
false
);
setError
(
""
);
}
}
type=
"text"
className=
{
`flex-1 py-2 rounded-lg text-sm font-medium transition ${
value=
{
username
}
!isRegister ? "bg-anton-accent text-white" : "text-anton-muted hover:text-white"
onChange=
{
(
e
)
=>
setUsername
(
e
.
target
.
value
)
}
}`
}
className=
"w-full bg-anton-card border border-anton-border rounded-xl px-4 py-3 text-white focus:outline-none focus:border-anton-accent transition"
>
placeholder=
"Enter username"
<
LogIn
size=
{
14
}
className=
"inline mr-1.5"
/>
required
Login
autoComplete=
"username"
</
button
>
autoCapitalize=
"off"
{
allowRegistration
&&
(
/>
<
button
onClick=
{
()
=>
{
setIsRegister
(
true
);
setError
(
""
);
}
}
className=
{
`flex-1 py-2 rounded-lg text-sm font-medium transition ${
isRegister ? "bg-anton-accent text-white" : "text-anton-muted hover:text-white"
}`
}
>
<
UserPlus
size=
{
14
}
className=
"inline mr-1.5"
/>
Register
</
button
>
)
}
</
div
>
</
div
>
{
isRegister
&&
(
{
error
&&
(
<
div
className=
"mb-4 bg-red-500/10 border border-red-500/30 rounded-lg px-3 py-2 flex items-start gap-2"
>
<
AlertCircle
size=
{
14
}
className=
"text-red-400 mt-0.5 shrink-0"
/>
<
span
className=
"text-red-400 text-xs"
>
{
error
}
</
span
>
</
div
>
)
}
<
form
onSubmit=
{
handleSubmit
}
className=
"space-y-4"
>
<
div
>
<
div
>
<
label
className=
"text-xs text-anton-muted mb-1
.5 block"
>
Email
</
label
>
<
label
className=
"text-xs text-anton-muted mb-1
block"
>
Username
</
label
>
<
input
<
input
type=
"email"
type=
"text"
value=
{
username
}
value=
{
email
}
onChange=
{
(
e
)
=>
setUsername
(
e
.
target
.
value
)
}
onChange=
{
(
e
)
=>
setEmail
(
e
.
target
.
value
)
}
className=
"w-full bg-anton-bg border border-anton-border rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-anton-accent transition"
className=
"w-full bg-anton-card border border-anton-border rounded-xl px-4 py-3 text-white focus:outline-none focus:border-anton-accent transition"
required
autoComplete=
"username"
placeholder=
"your@email.com"
required
autoComplete=
"email"
/>
/>
</
div
>
</
div
>
)
}
<
div
>
{
isRegister
&&
(
<
label
className=
"text-xs text-anton-muted mb-1.5 block"
>
Password
</
label
>
<
div
>
<
div
className=
"relative"
>
<
label
className=
"text-xs text-anton-muted mb-1 block"
>
Email
</
label
>
<
input
type=
"email"
value=
{
email
}
onChange=
{
(
e
)
=>
setEmail
(
e
.
target
.
value
)
}
className=
"w-full bg-anton-bg border border-anton-border rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-anton-accent transition"
required
autoComplete=
"email"
/>
</
div
>
)
}
<
div
>
<
label
className=
"text-xs text-anton-muted mb-1 block"
>
Password
</
label
>
<
input
<
input
type=
{
showPw
?
"text"
:
"password"
}
type=
"password"
value=
{
password
}
value=
{
password
}
onChange=
{
(
e
)
=>
setPassword
(
e
.
target
.
value
)
}
onChange=
{
(
e
)
=>
setPassword
(
e
.
target
.
value
)
}
className=
"w-full bg-anton-card border border-anton-border rounded-xl px-4 py-3 pr-12 text-white focus:outline-none focus:border-anton-accent transition"
className=
"w-full bg-anton-bg border border-anton-border rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-anton-accent transition"
placeholder=
"••••••••"
required
autoComplete=
{
isRegister
?
"new-password"
:
"current-password"
}
required
autoComplete=
{
isRegister
?
"new-password"
:
"current-password"
}
/>
/>
<
button
type=
"button"
onClick=
{
()
=>
setShowPw
(
!
showPw
)
}
className=
"absolute right-3 top-1/2 -translate-y-1/2 text-anton-muted hover:text-white transition p-1"
>
{
showPw
?
<
EyeOff
size=
{
18
}
/>
:
<
Eye
size=
{
18
}
/>
}
</
button
>
</
div
>
</
div
>
{
error
&&
(
<
div
className=
"bg-anton-danger/10 border border-anton-danger/30 text-anton-danger text-sm rounded-lg px-3 py-2.5"
>
{
error
}
</
div
>
</
div
>
)
}
<
button
<
button
type=
"submit"
type=
"submit"
disabled=
{
loading
}
disabled=
{
loading
}
className=
"w-full bg-anton-accent text-white py-2.5 rounded-lg font-medium hover:opacity-90 transition disabled:opacity-50"
className=
"w-full py-3.5 bg-anton-accent text-white rounded-xl font-semibold hover:opacity-90 transition disabled:opacity-50 active:scale-[0.98] flex items-center justify-center gap-2"
>
>
{
loading
?
"..."
:
isRegister
?
"Create Account"
:
"Sign In"
}
{
loading
&&
<
Loader2
size=
{
18
}
className=
"animate-spin"
/>
}
</
button
>
{
isRegister
?
"Create Account"
:
"Sign In"
}
</
form
>
</
button
>
<
button
{
!
allowRegistration
&&
configLoaded
&&
!
isRegister
&&
(
type=
"button"
<
p
className=
"text-[11px] text-anton-muted text-center mt-4"
>
onClick=
{
()
=>
{
setIsRegister
(
!
isRegister
);
setError
(
""
);
}
}
Registration is currently disabled by the administrator.
className=
"w-full text-center text-sm text-anton-muted hover:text-white transition py-2"
</
p
>
>
)
}
{
isRegister
?
"Already have an account? Sign in"
:
"Need an account? Register"
}
</
div
>
</
button
>
</
form
>
</
div
>
</
div
>
</
div
>
</
div
>
);
);
...
...
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