1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-09 03:36:48 +00:00
Commit graph

77 commits

Author SHA1 Message Date
catborise
0044359202 feat(accounts): the administrator's view of a user as an identity strip and cards
A superuser's page of a user was a heading, two unlabelled icon buttons
and three tabs: the VMs a table of check columns, the SSH keys a table
that pushed the page sideways on a phone.

Now a card at the top shows the user's name, role, Blocked when they
are, username, email, groups, last login and joining date, with Edit
user and Email OTP QR code as labelled buttons and links to the sections
below with their counts. The VMs card shows each VM with its host and
what the user may do beyond power and console as badges in the VM Users
tab's words (Change settings, Delete the VM, Change console settings),
with Change access and Remove from user in a menu, and Add VM in its
header. The SSH keys and the API tokens are cards; the key rows are now
one include with the user's own Profile, and both lists say when they
are empty. The full key shows over its fingerprint.

The tab script had no page left and goes.
2026-10-31 18:10:05 +03:00
catborise
3c7346c820 feat(accounts): the profile with details beside the user's keys and tokens
The Profile page was three tabs, each a box inside a box with horizontal
labels: the SSH keys a centred bare row with a grey trash icon, a tall
empty textarea under them, and the user known only by the heading.

Now the user's name, username, role, email and last login head a card
that holds the profile form and the password link; beside it the SSH
keys and the API tokens are cards with their count, a line on what
they are for, one row each with Delete in a menu, and the form to add
one behind a button. Adding or deleting a key returns to #sshkeys.

The token rows are a list-group include that the administrator's view
of a user shares.
2026-10-31 18:10:05 +03:00
catborise
cd2dd517b7 fix(instances): two owners added at once keep a VM to one owner
With one owner per VM, the VM page and a user's page counted the VM's
owners and then added one in separate steps, so two owners added at the
same moment both found the VM without one, and it got two. The check
and the insert now run in one transaction that holds the ownership lock
(PostgreSQL's advisory lock; SQLite's IMMEDIATE transactions already
serialize them): the second waits for the first and is refused. The
audit row is written after the transaction, so a failure keeps its row.
2026-10-31 00:21:06 +03:00
catborise
3d632f1592 feat(instances): a VM's owners with their rights, and XML edits kept until saved
Settings > Users was a bold title, an unlabelled "+" and a table of
names with a trash icon. The owner chose this layout from three
rendered options: one list of owners, each with what it may do
(change settings, delete the VM, change console settings, or power and
console only), Edit rights and Remove, an empty state, and a labelled
"Add owner".

- Remove and Edit rights come back to the VM's Users tab: the return
  address carried #users unencoded, so it was lost, and Edit rights
  went to the user's account page. Both accounts views now go to a
  ?next page of this site.
- The add-owner list offers only users who are not owners yet and not
  administrators, who can use every VM anyway; a superuser owner also
  took the only place when a VM may have one owner.
- The unused del_owner view and URL are removed: the list uses the
  accounts page, which asks before removing an owner.

XML:

- An XML edit not saved yet is kept: the page no longer reloads itself
  when the VM's state changes, and the browser asks before leaving it.
  A state change lost the edit.
- The button that opens the review dialog reads "Review changes"; it
  did not save, as "Change" suggested. Closing the dialog gives focus
  back to the editor.
2026-10-30 17:02:38 +03:00
catborise
93d3cc7238 fix(accounts): SSH keys with spaced comments, one token per create, OTP errors
- An SSH public key whose comment holds spaces (ssh-keygen -C "John Doe
  laptop") is accepted; only the type and the key are checked.
- The created API token is shown by a GET after a redirect, so reloading
  the page no longer creates a second token the user never sees.
- The OTP login page shows field errors instead of an empty error box.
- A user without a local password (an LDAP user) is not offered a password
  change that could never succeed.
2026-10-26 16:14:37 +03:00
catborise
b619cf3cd4 fix(accounts): a user's page shows permissions as marks, says when there are no VMs, deletes keys
On a superuser's page of a user the VM permissions read True/False; they
are now check marks as on the Users list. A user without VMs gets "No VMs."
instead of an empty table. A superuser can delete one of the user's SSH
keys, as with API tokens: the same view takes the user, the row names
whose key it was.

Closes #218
2026-10-26 11:48:40 +03:00
catborise
d80ec72fe0 fix(accounts): a user's page reads only that user's VMs
The administrator's view of a user loaded every VM in the panel and never
used them; the user's own VMs are now read in one query.
2026-10-26 11:19:42 +03:00
catborise
0e8130f1b0 fix(admin): the Users pages keep an administrator in and say what their fields mean
- An administrator could block, delete or demote (unset Active or
  Superuser on) their own account, and with it the last one, leaving the
  panel without anyone to undo it. These are now refused.
- The user form showed "No password set." for users who have one: its
  password field never read the user's password. Its "Set password"
  link went to ../password/, a page this panel does not have. The form
  now says whether a password is set and links the page that sets it.
- "Staff status" said "can log into this admin site"; here it lets a
  user change and delete the template VMs given to them, and is now
  called Template editor.
- A quota of 0 counted as no limit, like -1, so a user's quota could not
  be taken away. Now -1 has no limit and 0 allows none; the help says
  so, and the labels give the units: memory in MiB, disk in GiB.
- A user's page for a user that does not exist answered with a server
  error instead of not found.
- The user list said "None" for a user who never logged in; it says
  "never", and dates fit their column.
- The password form's labels no longer break mid-word.
2026-10-25 22:38:40 +03:00
catborise
85b5c769e9 feat(accounts): a superuser sees and deletes a user's API tokens
The user page a superuser opens from the user list has an API Tokens
tab: the user's tokens with their name, first characters, creation,
expiry and last use, and a delete button for one that may have leaked.
The token itself is never shown; only its digest is kept.

The delete reuses the owner's delete view with the user's id; only a
superuser may name another user. The confirmation and the log row say
whose token it was ("API token backup of alice"); the owner's own
confirmation now reads "Delete "API token backup"?" too. The profile
and the user page share one token table, and one script that opens a
tab named in the address (#apitokens).
2026-10-25 14:13:47 +03:00
catborise
6b51c30dd6 feat(accounts): API tokens show their last use, and a user has at most ten
The API Tokens tab gets a "Last used" column, so an unused token can be
told from one a script still needs. The token's last use is written when
it authenticates, at most once a minute (accounts.auth.TokenAuthentication,
knox's class with that one addition; the API schema describes it as
knox's).

A user has at most ten tokens; a new one is refused until one is
deleted. Expired tokens do not count, and are deleted when a new token is
made.
2026-10-25 10:16:52 +03:00
catborise
1c60696ea1 feat(accounts): API tokens for scripts and tools
The REST API took only the login session, so a script could not use it
without a browser. A user now makes API tokens on the profile page (API
Tokens tab): a name, and an expiry of 30, 90 or 365 days or none. The
token is shown once, on a page that is not cached, and then kept only as
a SHA-512 digest; the list shows its name, first characters, creation
and expiry, and a token is deleted there. Tools send it as
"Authorization: Token wvc_...", and act as its user, with that user's
permissions. Making and deleting a token is logged, without the token.

A password alone (HTTP Basic) still does not reach the API, as it would
skip the OTP second factor: a token is made in a session that passed the
login and its OTP. The token class comes first, so a refused request gets
401 with its challenge instead of 403.

django-rest-knox 5.1.0 provides the tokens (accounts.ApiToken adds the
name); settings.py takes "knox", KNOX_TOKEN_MODEL and REST_KNOX from the
template.
2026-10-24 23:14:27 +03:00
catborise
c1d36053d6 feat(logs): every VM operation writes an audit row the VM's Logs tab finds by UUID
Operations on a VM now write their rows the way the admin audit does: one
row per operation, done or failed with a safe reason, with the user, IP and
host. Each row names the VM ("VM web-1: delete snapshot snap1") and carries
its UUID in the new Logs.instance_uuid column.

- audit_vm and failed_vm wrap audit() and failed() for a VM. A failure the
  view turns into a message is caught outside the block, so its failed row
  is written; busy claims, permission and quota refusals and invalid input
  write nothing. No password, key or XML goes into a row.
- All VM page operations move from addlogmsg to audit_vm, and addlogmsg
  goes. QoS changes and defining a VM from XML are logged now; a gstfsd
  error reply (root password, SSH key) is a failed row, not a done one.
- Partial results are rows of their own: the VM delete, then each disk
  delete; the disk detach, then the volume delete; the migration, then a
  failed autostart restore, NVRAM removal or record on the target.
- Migration rows use the compute name as the host column, as every other
  row does.
- VM job rows (create, clone, snapshots) and their resolutions carry the
  VM's UUID; snapshot job intents now record it. The owner rows of the VM
  page and the accounts pages, and the ownership system rows, carry it too.
- The Logs tab loads vm_logs/<pk>/ and shows the rows with the VM's UUID,
  so a rename keeps the history, two VMs with one name are told apart and
  a VM on two computes is one VM. It gains a Result column. Rows from
  before this change have no UUID and stay on the Logs page only.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 14:14:53 +03:00
catborise
e04adec87b feat(logs): changes to users, groups, owners, flavors and settings are logged
The Logs page now records the panel's own records too: users (add,
change with the changed fields, delete, block, unblock, an admin's
password set, a user's own password change including the first-login
one, own profile edits), groups with their members and permissions,
VM owners from the accounts pages and from the VM page, OTP QR code
mails, flavors from web and API, app settings and the theme,
removed-VM records and SSH keys. No row carries a password or a key.

- A user is created in one transaction: when a VM named in
  NEW_USER_DEFAULT_INSTANCES is missing, no half-made user stays.
- The OTP mail checks the email address before it creates a device;
  without one it writes a failed row and makes no device.
- The VM page's owner rows read "VM web-1 owner bob: add" and
  "...: remove" instead of "Add owner: bob" and "Delete owner: 17"; a
  second owner refused by the single-owner setting is a failed row.
  Owner rows keep the VM's host and name, so the VM's log tab shows
  them.
- A setting or theme posted with its current value is no change and
  writes no row; the old free-text setting rows are gone.

Host-side objects (networks, storage, interfaces, secrets, filters)
follow in the next layer.
2026-10-23 11:10:24 +03:00
catborise
8c0fbff8f7 feat(logs): admin changes and logins are recorded on the Logs page
The Logs page shows who added, changed or deleted a compute, from web
or API, and every login, failed login, lockout and logout, with the
client address and a Result column (Done, Failed, Unknown for older
rows). A failed change is recorded with its reason and the error still
reaches the user; a refused one is recorded too.

- logs.audit.write saves each row in its own savepoint and never
  raises, so a log write cannot break or undo the action it records.
  The audit() context manager writes one row when the change ends.
- Logs gains success and ip; user, host and instance are wider and
  date is indexed. The page orders by date then id.
- A compute edit names the changed fields; an edit that changes
  nothing writes no row, and an empty password field is no change.
- A compute's VMs leaving the panel get a system row each: owners
  kept on the Removed VMs page, moved to another compute, restored,
  left without owners, or the removed record expired.
- Job rows carry the result. A job's log entry that cannot be
  computed is logged and skipped instead of rolling back the finish
  (test_service.py changes this on purpose).
- Logins are recorded by signal receivers; CustomLoginView is removed
  and the plain LoginView is used. A wrong OTP code is a failed login,
  counted by django-axes toward the lockout.

Panel records and host-side objects follow in the next layers.
2026-10-23 10:30:48 +03:00
catborise
fbe93d9aee fix(templates): each delete page says what deleting its object does
The shared delete page asked only whether to delete the object. Each
view now gives its consequence: a deleted compute's VMs keep running
but the panel forgets them and their owners, a deleted user's VMs
stay, a group's members keep their accounts, a VM's owner loses only
the access, a removed VM's record no longer brings its owners back,
an SSH key stays on the VMs that have it, a flavor only goes as a
size template.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 11:15:00 +03:00
catborise
961b43a42e fix(accounts): a signed-in user is sent on from the login page
The login page showed its sign-in card to a user who was already signed
in, with the navbar squeezed beside it. Both login views, with and
without OTP, now send that user to the page in next, or to the default
page when next is missing or points elsewhere. Every permission check
answers 403 instead of redirecting to the login, so this cannot loop.
The page test renders the login page signed out.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 20:33:44 +03:00
catborise
40287dbd2a fix(accounts): OTP login works per user and "Lost OTP?" cannot impose 2FA
With OTP_ENABLED the login used django-otp's OTPAuthenticationForm. Under the
pinned django-otp 1.7.3 that form could not log anyone in through the shipped
page: the page posts only a 6-digit otp_token, but the form now needs the
device named explicitly, so an enrolled user got "Please select a device.",
and a user who never enrolled got "Please enter your OTP token." with no
device to produce one. Verified in an isolated test database.

SingleDeviceOTPForm makes OTP per user: a user with a confirmed TOTP device
must enter a valid code; a user without one logs in with the password alone.
The form chooses the device itself (the user's oldest confirmed TOTP device,
locked with select_for_update inside clean_otp's transaction so replay and
throttling counters cannot race) and ignores any posted otp_device, which
django-otp does not check for confirmed status or type. The login page drops
the HTML "required" on the token field (the field is optional for an
unenrolled user) and says so.

The "Lost OTP?" page (email_otp) created a confirmed device when none existed.
With per-user OTP that let anyone who knows an address turn a password-only
account into an OTP-required one and, if the mail never arrived, lock it out.
It now resends the QR only to a user who already enrolled and never creates a
device (get_existing_totp_device); admin enrollment (admin_email_otp) still
creates one. Email QR distribution and confirmed-on-create enrollment are
unchanged by user decision.

Tests: per-user login (no device -> password only; device -> valid token
passes, wrong/empty token fails), a posted or another user's device is not
used, a token cannot be replayed, and the real login page over HTTP with
OTP_ENABLED; email_otp sends nothing and creates no device for an unenrolled
user. The new tests fail on the previous code.
2026-10-07 08:48:55 +03:00
catborise
da63e570e5 fix(accounts): the "Lost OTP?" page cannot be used to flood or break
/accounts/email_otp/ mails a user their OTP QR code and needs no login.
Measured with the default settings (OTP disabled):

- It was reachable although OTP was off, and created OTP devices.
- Every request sent a mail: 20 requests for one address, 20 mails, so
  anyone who knew an address could fill that mailbox.
- An address shared by two users, or a mail server that could not be
  reached, ended in an error page (500).
- Inactive users were mailed too.

The page now exists only while OTP_ENABLED is set. It mails a single
active user whose address matches (case-insensitively), at most once
every 5 minutes per user (Django's cache: with several workers the
bound is per worker), and logs a failed delivery. The answer is the
same in every case, so it does not reveal whether an address belongs
to a user.

The QR codes come from django-qr-code (segno); both the mail and the
account page render them without the qrcode package removed earlier.
The new tests fail on the previous code.
2026-10-05 20:06:02 +03:00
catborise
e94d69a524 feat(accounts): Django's LoginRequiredMiddleware replaces the package
django-login-required-middleware is dropped; Django 5.1+ ships the same
middleware. The login page is already marked login_not_required by
Django; email_otp gets the decorator, which replaces
LOGIN_REQUIRED_IGNORE_VIEW_NAMES. The datasource still requires login.

Existing settings.py files still name the old middleware. A system
check (accounts.E002) stops migrate and the server until MIDDLEWARE
uses Django's middleware, instead of an ImportError at startup; the
README has the steps.

A new matrix test walks every URL anonymously: everything outside a
short allowlist (login pages, the OpenAPI docs) sends the visitor to
the login page, and API views deny with 401/403. It passed with the old
middleware first and passes unchanged now.
2026-10-04 01:08:11 +03:00
catborise
b04f656d2b ci: replace super-linter with ruff
Super-linter ran from the unpinned :latest image and, because of
branches-ignore: [master], never on pull requests to master. Ruff now
checks syntax errors and pyflakes rules (E9, F) on every push and PR.

Fixes the existing findings: unused imports and variables, an unused
duplicate import in instances/tests.py, and the star import in
accounts/views.py. settings-dev.py keeps its star import of the base
settings.
2026-10-04 01:08:11 +03:00
catborise
6dabd69445 fix(accounts): first-login password change is stored, not in the session
The forced change of the generated admin password was a session flag
set at login when the password still matched data/admin_password. A
new session after the file was gone bypassed it.

- UserAttributes.must_change_password (migration 0007) holds it; the
  admin created with a generated password gets it, and a password
  change clears it. ForcePasswordChangeMiddleware reads it.
- A system check (accounts.E001) stops the server unless the middleware
  follows AuthenticationMiddleware; the README has the settings change.
- If the generated password cannot be stored privately, provisioning
  stops without printing it and without creating the admin (one
  transaction for the user and its attributes).
- The admin is provisioned on any real migrate run while no user
  exists, so running migrate again after a failure creates it; flush
  (plan=None) and rollbacks of accounts are skipped.
- The post_migrate receivers check the migration state for the models
  they use, so a targeted migrate on a fresh database (e.g. migrate
  appsettings) no longer fails with 'no such table'.
2026-10-04 01:08:11 +03:00
catborise
78852eb4c8 feat(accounts): force a password change at the first login with the generated password
While data/admin_password exists and a user's password still matches it,
the login sets a session flag (checked once at login, not per request).
ForcePasswordChangeMiddleware then sends that user to the password-change
page for every view except change password, login and logout. Setting a
new password clears the flag and deletes data/admin_password. No schema
change; if ADMIN_PASSWORD was set, there is no file and nothing is forced.
2026-10-02 23:55:31 +03:00
catborise
cf1cde376b fix: require POST for user block/unblock and OTP email, flag console cookie
- user_block, user_unblock and admin_email_otp changed state on GET, so an
  <img> tag could lock a user out or trigger OTP emails through an
  admin's session. They now require POST; the buttons are CSRF forms.
- The console token cookie is set with httponly, SameSite=Lax and, on
  HTTPS, secure. Only novncd reads it; the page gets the token directly.
2026-10-02 22:44:00 +03:00
catborise
b3e6fb2b7a feat(security): enforce require_POST on power actions, prevent open redirects, and isolate instance permissions 2026-09-27 13:20:45 +03:00
Emre Serdengeçti
1cbdf76df6
logger updates (#31)
* Log for failed login attempts

* Logger configuration for logging to file

* interface fixes

* login log fix, added logged in too

* bootstrap icons setup

* font-awesome icons replaced with bootstrap icons

* replaced i-tags with django_bootstrap_icons

* removed icons library from project

* bug fix

---------

Co-authored-by: catborise <catborise@gmail.com>
2023-08-09 09:20:18 +03:00
catborise
a1eab70e2d reorg imports and black format 2022-11-02 16:05:41 +03:00
catborise
217e106c8b format python code with black 2022-11-02 08:54:35 +03:00
catborise
508e3609be lint with black python. convert f style strings to old one. some small fixes 2020-11-05 12:34:31 +03:00
Real-Gecko
1f26bd0c31 Do not send email if user email is not set 2020-10-19 17:11:28 +06:00
Real-Gecko
5a19f0c949 OTP improvements 2020-10-19 14:26:08 +06:00
Real-Gecko
5172a9f619 Accounts app improvements and tests 2020-10-14 14:25:05 +03:00
Real-Gecko
0052323190 Implemented OTP #341 2020-10-08 17:57:51 +06:00
catborise
922a8e62d1 fix some linter warnings 2020-09-25 15:57:17 +03:00
catborise
aee7fa34b8 fix user not found while editing user profile 2020-09-25 09:21:37 +03:00
Real-Gecko
47009d47ca Instances overhaul 2020-07-13 15:33:09 +06:00
Real-Gecko
636b5bb1bc Reworked some account views 2020-06-16 18:35:08 +06:00
Real-Gecko
b85e246aed Moved password changing to a separate view 2020-06-16 14:36:53 +06:00
Real-Gecko
20d4aaac30 Removed UserAddForm as it is not used 2020-06-16 11:09:55 +06:00
catborise
e46bb99b3b fix typos and rearrange imports & add new lines EOF 2020-06-04 15:22:55 +03:00
catborise
7538ddab4f Add all settings to appsettings page & Convert settings choices 2020-06-04 12:32:00 +03:00
catborise
0e5840498e bootstrap 4.5 & Bootswatch Themes 2020-06-04 11:33:25 +03:00
Real-Gecko
a62daad87b Added change password permission
Replaces SHOW_PROFILE_EDIT_PASSWORD option
Can be set on per user or per group basis
2020-05-27 18:46:37 +06:00
Real-Gecko
27f62dff6c Added admin application
- Manage users
- Manage groups
- Manage logs
2020-05-27 18:24:06 +06:00
Real-Gecko
38befa4362 Added django-login-required-middleware
Thus eliminating need for login_requred decorator on every view
2020-05-27 12:43:29 +06:00
catborise
a6f7618854 convert django.core.urlresolver to django.urls. fix i18n. fix identation 2020-01-24 10:09:46 +03:00
catborise
828a271789 pep8 style code corrections 2019-09-10 09:48:31 +03:00
Ing. Jan KRCMAR
b909b9d0f1 Merge remote-tracking branch 'retspen/master' into upgrade_django 2018-07-26 12:44:22 +02:00
Ing. Jan KRCMAR
9f7ec62226 accounts: add public key check 2018-07-26 11:33:12 +02:00
catborise
ace30ca952 Libxml2 converted to lxml, django framework upgrade to 1.11.x and some bug fixes 2018-06-27 10:20:51 +03:00
Ing. Jan KRCMAR
43a8fb6dc1 add accounts view style list. configurable via settings.VIEW_ACCOUNTS_STYLE 2018-05-15 14:22:10 +02:00