feat: merge auth&tester

This commit is contained in:
Vyacheslav1557 2025-04-22 20:44:52 +05:00
parent 0a2dea6c23
commit 441af4c6a2
72 changed files with 4910 additions and 2378 deletions

View file

@ -24,6 +24,8 @@ type ContestsList struct {
type ContestsFilter struct {
Page int32
PageSize int32
UserId *int32
Order *int32
}
func (f ContestsFilter) Offset() int32 {
@ -53,3 +55,73 @@ type ProblemStatSummary struct {
Success int32 `db:"success"`
Total int32 `db:"total"`
}
type Task struct {
Id int32 `db:"id"`
Position int32 `db:"position"`
Title string `db:"title"`
TimeLimit int32 `db:"time_limit"`
MemoryLimit int32 `db:"memory_limit"`
ProblemId int32 `db:"problem_id"`
ContestId int32 `db:"contest_id"`
LegendHtml string `db:"legend_html"`
InputFormatHtml string `db:"input_format_html"`
OutputFormatHtml string `db:"output_format_html"`
NotesHtml string `db:"notes_html"`
ScoringHtml string `db:"scoring_html"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
type TasksListItem struct {
Id int32 `db:"id"`
ProblemId int32 `db:"problem_id"`
ContestId int32 `db:"contest_id"`
Position int32 `db:"position"`
Title string `db:"title"`
MemoryLimit int32 `db:"memory_limit"`
TimeLimit int32 `db:"time_limit"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
type Participant struct {
Id int32 `db:"id"`
UserId int32 `db:"user_id"`
ContestId int32 `db:"contest_id"`
Name string `db:"name"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
type ParticipantsListItem struct {
Id int32 `db:"id"`
UserId int32 `db:"user_id"`
ContestId int32 `db:"contest_id"`
Name string `db:"name"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
type ParticipantsList struct {
Participants []*ParticipantsListItem
Pagination Pagination
}
type ParticipantsFilter struct {
Page int32
PageSize int32
ContestId int32
}
func (f ParticipantsFilter) Offset() int32 {
return (f.Page - 1) * f.PageSize
}
type ParticipantUpdate struct {
Name *string `json:"name"`
}

View file

@ -1,41 +0,0 @@
package models
import "time"
type Participant struct {
Id int32 `db:"id"`
UserId int32 `db:"user_id"`
ContestId int32 `db:"contest_id"`
Name string `db:"name"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
type ParticipantsListItem struct {
Id int32 `db:"id"`
UserId int32 `db:"user_id"`
ContestId int32 `db:"contest_id"`
Name string `db:"name"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
type ParticipantsList struct {
Participants []*ParticipantsListItem
Pagination Pagination
}
type ParticipantsFilter struct {
Page int32
PageSize int32
ContestId int32
}
func (f ParticipantsFilter) Offset() int32 {
return (f.Page - 1) * f.PageSize
}
type ParticipantUpdate struct {
Name *string `json:"name"`
}

View file

@ -13,7 +13,6 @@ type Problem struct {
OutputFormat string `db:"output_format"`
Notes string `db:"notes"`
Scoring string `db:"scoring"`
LatexSummary string `db:"latex_summary"`
LegendHtml string `db:"legend_html"`
InputFormatHtml string `db:"input_format_html"`

View file

@ -1,20 +1,74 @@
package models
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"github.com/google/uuid"
"github.com/open-policy-agent/opa/v1/rego"
"strconv"
"time"
)
type Session struct {
Id string `json:"id"`
UserId int32 `json:"user_id"`
Role Role `json:"role"`
CreatedAt time.Time `json:"created_at"`
ExpiresAt time.Time `json:"expires_at"`
UserAgent string `json:"user_agent"`
Ip string `json:"ip"`
}
func (s *Session) Valid() error {
if uuid.Validate(s.Id) != nil {
return errors.New("invalid session id")
}
if s.UserId == 0 {
return errors.New("empty user id")
}
if s.CreatedAt.IsZero() {
return errors.New("empty created at")
}
if s.ExpiresAt.IsZero() {
return errors.New("empty expires at")
}
if s.UserAgent == "" {
return errors.New("empty user agent")
}
if s.Ip == "" {
return errors.New("empty ip")
}
return nil
}
func (s *Session) JSON() ([]byte, error) {
return json.Marshal(s)
}
func (s *Session) UserIdHash() string {
return sha256string(strconv.FormatInt(int64(s.UserId), 10))
}
func (s *Session) SessionIdHash() string {
return sha256string(s.Id)
}
func (s *Session) Key() string {
return fmt.Sprintf("userid:%s:sessionid:%s", s.UserIdHash(), s.SessionIdHash())
}
func sha256string(s string) string {
hasher := sha256.New()
hasher.Write([]byte(s))
return hex.EncodeToString(hasher.Sum(nil))
}
type JWT struct {
SessionId string `json:"session_id"`
UserId int32 `json:"user_id"`
Role Role `json:"role"`
ExpiresAt int64 `json:"exp"`
IssuedAt int64 `json:"iat"`
NotBefore int64 `json:"nbf"`
Permissions []grant `json:"permissions"`
SessionId string `json:"session_id"`
UserId int32 `json:"user_id"`
Role Role `json:"role"`
IssuedAt int64 `json:"iat"`
}
func (j JWT) Valid() error {
@ -24,139 +78,18 @@ func (j JWT) Valid() error {
if j.UserId == 0 {
return errors.New("empty user id")
}
if j.ExpiresAt == 0 {
return errors.New("empty expires at")
}
if j.IssuedAt == 0 {
return errors.New("empty issued at")
}
if j.NotBefore == 0 {
return errors.New("empty not before")
}
if len(j.Permissions) == 0 {
return errors.New("empty permissions")
}
return nil
}
type Role int32
const (
RoleGuest Role = -1
RoleStudent Role = 0
RoleTeacher Role = 1
RoleAdmin Role = 2
)
func (r Role) String() string {
switch r {
case RoleGuest:
return "guest"
case RoleStudent:
return "student"
case RoleTeacher:
return "teacher"
case RoleAdmin:
return "admin"
}
panic("invalid role")
type Credentials struct {
Username string
Password string
}
type Action string
const (
Create Action = "create"
Read Action = "read"
Update Action = "update"
Delete Action = "delete"
)
type Resource string
const (
ResourceAnotherUser Resource = "another-user"
ResourceMeUser Resource = "me-user"
ResourceListUser Resource = "list-user"
ResourceOwnSession Resource = "own-session"
)
type grant struct {
Action Action `json:"action"`
Resource Resource `json:"resource"`
}
var Grants = map[string][]grant{
RoleGuest.String(): {},
RoleStudent.String(): {
{Read, ResourceAnotherUser},
{Read, ResourceMeUser},
{Update, ResourceOwnSession},
{Delete, ResourceOwnSession},
},
RoleTeacher.String(): {
{Create, ResourceAnotherUser},
{Read, ResourceAnotherUser},
{Read, ResourceMeUser},
{Read, ResourceListUser},
{Update, ResourceOwnSession},
{Delete, ResourceOwnSession},
},
RoleAdmin.String(): {
{Create, ResourceAnotherUser},
{Read, ResourceAnotherUser},
{Read, ResourceMeUser},
{Read, ResourceListUser},
{Update, ResourceAnotherUser},
{Update, ResourceOwnSession},
{Delete, ResourceAnotherUser},
{Delete, ResourceOwnSession},
},
}
const module = `package app.rbac
default allow := false
allow if {
some grant in input.role_grants[input.role]
input.action == grant.action
input.resource == grant.resource
}
`
var query rego.PreparedEvalQuery
func (r Role) HasPermission(action Action, resource Resource) bool {
ctx := context.TODO()
input := map[string]interface{}{
"action": action,
"resource": resource,
"role": r.String(),
"role_grants": Grants,
}
results, err := query.Eval(ctx, rego.EvalInput(input))
if err != nil {
panic(err)
}
return results.Allowed()
}
func init() {
var err error
ctx := context.TODO()
query, err = rego.New(
rego.Query("data.app.rbac.allow"),
rego.Module("ms-auth.rego", module),
).PrepareForEval(ctx)
if err != nil {
panic(err)
}
type Device struct {
Ip string
UseAgent string
}

View file

@ -31,6 +31,7 @@ type Solution struct {
type SolutionCreation struct {
Solution string
TaskId int32
UserId int32
ParticipantId int32
Language int32
Penalty int32

View file

@ -1,35 +0,0 @@
package models
import "time"
type Task struct {
Id int32 `db:"id"`
Position int32 `db:"position"`
Title string `db:"title"`
TimeLimit int32 `db:"time_limit"`
MemoryLimit int32 `db:"memory_limit"`
ProblemId int32 `db:"problem_id"`
ContestId int32 `db:"contest_id"`
LegendHtml string `db:"legend_html"`
InputFormatHtml string `db:"input_format_html"`
OutputFormatHtml string `db:"output_format_html"`
NotesHtml string `db:"notes_html"`
ScoringHtml string `db:"scoring_html"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
type TasksListItem struct {
Id int32 `db:"id"`
ProblemId int32 `db:"problem_id"`
ContestId int32 `db:"contest_id"`
Position int32 `db:"position"`
Title string `db:"title"`
MemoryLimit int32 `db:"memory_limit"`
TimeLimit int32 `db:"time_limit"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}

71
internal/models/user.go Normal file
View file

@ -0,0 +1,71 @@
package models
import (
"golang.org/x/crypto/bcrypt"
"time"
)
type Role int32
type User struct {
Id int32 `db:"id"`
Username string `db:"username"`
HashedPassword string `db:"hashed_pwd"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
Role Role `db:"role"`
}
type UserCreation struct {
Username string
Password string
Role Role
}
func (u *UserCreation) HashPassword() error {
hpwd, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
if err != nil {
return err
}
u.Password = string(hpwd)
return nil
}
func (user *User) IsSamePwd(password string) bool {
err := bcrypt.CompareHashAndPassword([]byte(user.HashedPassword), []byte(password))
if err != nil {
return false
}
return true
}
type UsersListFilters struct {
PageSize int32
Page int32
}
func (f UsersListFilters) Offset() int32 {
return (f.Page - 1) * f.PageSize
}
type UsersList struct {
Users []*User
Pagination Pagination
}
type UserUpdate struct {
Username *string
Role *Role
}
const (
RoleGuest Role = -1
RoleStudent Role = 0
RoleTeacher Role = 1
RoleAdmin Role = 2
)
type Grant struct {
Action string
Resource string
}