ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m15s
ci / api / api (push) Successful in 1m31s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 47s
ci / ts / ts (packages + web) (push) Successful in 39s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 26s
- users, refresh_tokens and audit_log schema (migration 00002) - argon2id passwords with rehash on login, password policy, lockout with exponential back-off and a per-IP rate limit in Redis - Ed25519 JWT access tokens with kid-based key rotation; rotating refresh tokens with reuse detection (cookie for web, body for Android) - bootstrap admin on first start; must_change_credentials and must_change_password gate every other endpoint - /auth/login, /auth/refresh, /auth/logout(-all), /me, /me/password, /me/bootstrap-credentials, /me/sessions; server-info reports bootstrap_required - role policy from docs/auth-and-roles.md with table tests - problem+json errors with field errors; ru/en translations of the codes - install.sh and update.sh generate JWT_PRIVATE_KEY Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
92 lines
3.2 KiB
Go
92 lines
3.2 KiB
Go
package auth
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestCan(t *testing.T) {
|
|
// Строки матрицы прав из docs/auth-and-roles.md: user, moderator, admin.
|
|
matrix := []struct {
|
|
perm Permission
|
|
name string
|
|
want [3]bool
|
|
}{
|
|
{PermAdminPanel, "admin panel", [3]bool{false, true, true}},
|
|
{PermListUsers, "list users", [3]bool{false, true, true}},
|
|
{PermManageLibrary, "manage library", [3]bool{false, false, true}},
|
|
{PermViewLibraryStatus, "library status", [3]bool{false, true, true}},
|
|
{PermSuggestTracks, "suggest tracks", [3]bool{false, true, true}},
|
|
{PermReviewUploads, "review uploads", [3]bool{false, false, true}},
|
|
{PermSystemStatus, "system status", [3]bool{false, false, true}},
|
|
}
|
|
roles := [3]Role{RoleUser, RoleModerator, RoleAdmin}
|
|
for _, row := range matrix {
|
|
for i, r := range roles {
|
|
t.Run(fmt.Sprintf("%s/%s", row.name, r), func(t *testing.T) {
|
|
assert.Equal(t, row.want[i], Can(r, row.perm))
|
|
})
|
|
}
|
|
}
|
|
assert.False(t, Can(Role("guest"), PermAdminPanel))
|
|
}
|
|
|
|
func TestCanManageUser(t *testing.T) {
|
|
actions := map[UserAction]string{
|
|
UserCreate: "create", UserEdit: "edit", UserResetPassword: "reset", UserBlock: "block",
|
|
UserChangeRole: "role", UserDelete: "delete",
|
|
}
|
|
// want[actor][target] для каждого действия; порядок ролей — user, moderator, admin.
|
|
cases := map[UserAction][3][3]bool{
|
|
UserCreate: {{false, false, false}, {true, false, false}, {true, true, true}},
|
|
UserEdit: {{false, false, false}, {true, false, false}, {true, true, true}},
|
|
UserResetPassword: {{false, false, false}, {true, false, false}, {true, true, true}},
|
|
UserBlock: {{false, false, false}, {true, false, false}, {true, true, true}},
|
|
UserChangeRole: {{false, false, false}, {false, false, false}, {true, true, true}},
|
|
UserDelete: {{false, false, false}, {false, false, false}, {true, true, true}},
|
|
}
|
|
roles := [3]Role{RoleUser, RoleModerator, RoleAdmin}
|
|
for action, want := range cases {
|
|
for ai, ar := range roles {
|
|
for ti, tr := range roles {
|
|
t.Run(fmt.Sprintf("%s/%s->%s", actions[action], ar, tr), func(t *testing.T) {
|
|
actor := Actor{ID: uuid.New(), Role: ar}
|
|
target := Actor{ID: uuid.New(), Role: tr}
|
|
assert.Equal(t, want[ai][ti], CanManageUser(actor, target, action))
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCanManageUserSelf(t *testing.T) {
|
|
admin := Actor{ID: uuid.New(), Role: RoleAdmin}
|
|
for _, a := range []UserAction{UserBlock, UserDelete, UserChangeRole} {
|
|
assert.False(t, CanManageUser(admin, admin, a), "action %d on self", a)
|
|
}
|
|
assert.True(t, CanManageUser(admin, admin, UserEdit))
|
|
assert.True(t, CanManageUser(admin, admin, UserResetPassword))
|
|
}
|
|
|
|
func TestCanAssignRole(t *testing.T) {
|
|
cases := []struct {
|
|
actor, role Role
|
|
want bool
|
|
}{
|
|
{RoleAdmin, RoleAdmin, true},
|
|
{RoleAdmin, RoleModerator, true},
|
|
{RoleAdmin, RoleUser, true},
|
|
{RoleAdmin, Role("root"), false},
|
|
{RoleModerator, RoleUser, true},
|
|
{RoleModerator, RoleModerator, false},
|
|
{RoleModerator, RoleAdmin, false},
|
|
{RoleUser, RoleUser, false},
|
|
}
|
|
for _, tc := range cases {
|
|
assert.Equal(t, tc.want, CanAssignRole(tc.actor, tc.role), "%s assigns %s", tc.actor, tc.role)
|
|
}
|
|
}
|