Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/internal/model/user.go
1560 views
1
package model
2
3
import (
4
"encoding/binary"
5
"encoding/json"
6
"fmt"
7
"time"
8
9
"github.com/alist-org/alist/v3/internal/errs"
10
"github.com/alist-org/alist/v3/pkg/utils"
11
"github.com/alist-org/alist/v3/pkg/utils/random"
12
"github.com/go-webauthn/webauthn/webauthn"
13
"github.com/pkg/errors"
14
)
15
16
const (
17
GENERAL = iota
18
GUEST // only one exists
19
ADMIN
20
NEWGENERAL
21
)
22
23
const StaticHashSalt = "https://github.com/alist-org/alist"
24
25
type User struct {
26
ID uint `json:"id" gorm:"primaryKey"` // unique key
27
Username string `json:"username" gorm:"unique" binding:"required"` // username
28
PwdHash string `json:"-"` // password hash
29
PwdTS int64 `json:"-"` // password timestamp
30
Salt string `json:"-"` // unique salt
31
Password string `json:"password"` // password
32
BasePath string `json:"base_path"` // base path
33
Role Roles `json:"role" gorm:"type:text"` // user's roles
34
RolesDetail []Role `json:"-" gorm:"-"`
35
Disabled bool `json:"disabled"`
36
// Determine permissions by bit
37
// 0: can see hidden files
38
// 1: can access without password
39
// 2: can add offline download tasks
40
// 3: can mkdir and upload
41
// 4: can rename
42
// 5: can move
43
// 6: can copy
44
// 7: can remove
45
// 8: webdav read
46
// 9: webdav write
47
// 10: ftp/sftp login and read
48
// 11: ftp/sftp write
49
// 12: can read archives
50
// 13: can decompress archives
51
// 14: check path limit
52
Permission int32 `json:"permission"`
53
OtpSecret string `json:"-"`
54
SsoID string `json:"sso_id"` // unique by sso platform
55
Authn string `gorm:"type:text" json:"-"`
56
}
57
58
func (u *User) IsGuest() bool {
59
return u.Role.Contains(GUEST)
60
}
61
62
func (u *User) IsAdmin() bool {
63
return u.Role.Contains(ADMIN)
64
}
65
66
func (u *User) ValidateRawPassword(password string) error {
67
return u.ValidatePwdStaticHash(StaticHash(password))
68
}
69
70
func (u *User) ValidatePwdStaticHash(pwdStaticHash string) error {
71
if pwdStaticHash == "" {
72
return errors.WithStack(errs.EmptyPassword)
73
}
74
if u.PwdHash != HashPwd(pwdStaticHash, u.Salt) {
75
return errors.WithStack(errs.WrongPassword)
76
}
77
return nil
78
}
79
80
func (u *User) SetPassword(pwd string) *User {
81
u.Salt = random.String(16)
82
u.PwdHash = TwoHashPwd(pwd, u.Salt)
83
u.PwdTS = time.Now().Unix()
84
return u
85
}
86
87
func (u *User) CanSeeHides() bool {
88
return u.Permission&1 == 1
89
}
90
91
func (u *User) CanAccessWithoutPassword() bool {
92
return (u.Permission>>1)&1 == 1
93
}
94
95
func (u *User) CanAddOfflineDownloadTasks() bool {
96
return (u.Permission>>2)&1 == 1
97
}
98
99
func (u *User) CanWrite() bool {
100
return (u.Permission>>3)&1 == 1
101
}
102
103
func (u *User) CanRename() bool {
104
return (u.Permission>>4)&1 == 1
105
}
106
107
func (u *User) CanMove() bool {
108
return (u.Permission>>5)&1 == 1
109
}
110
111
func (u *User) CanCopy() bool {
112
return (u.Permission>>6)&1 == 1
113
}
114
115
func (u *User) CanRemove() bool {
116
return (u.Permission>>7)&1 == 1
117
}
118
119
func (u *User) CanWebdavRead() bool {
120
return (u.Permission>>8)&1 == 1
121
}
122
123
func (u *User) CanWebdavManage() bool {
124
return (u.Permission>>9)&1 == 1
125
}
126
127
func (u *User) CanFTPAccess() bool {
128
return (u.Permission>>10)&1 == 1
129
}
130
131
func (u *User) CanFTPManage() bool {
132
return (u.Permission>>11)&1 == 1
133
}
134
135
func (u *User) CanReadArchives() bool {
136
return (u.Permission>>12)&1 == 1
137
}
138
139
func (u *User) CanDecompress() bool {
140
return (u.Permission>>13)&1 == 1
141
}
142
143
func (u *User) CheckPathLimit() bool {
144
return (u.Permission>>14)&1 == 1
145
}
146
147
func (u *User) JoinPath(reqPath string) (string, error) {
148
if reqPath == "/" {
149
return utils.FixAndCleanPath(u.BasePath), nil
150
}
151
path, err := utils.JoinBasePath(u.BasePath, reqPath)
152
if err != nil {
153
return "", err
154
}
155
156
if path != "/" && u.CheckPathLimit() {
157
basePaths := GetAllBasePathsFromRoles(u)
158
match := false
159
for _, base := range basePaths {
160
if utils.IsSubPath(base, path) {
161
match = true
162
break
163
}
164
}
165
if !match {
166
return "", errs.PermissionDenied
167
}
168
}
169
170
return path, nil
171
}
172
173
func StaticHash(password string) string {
174
return utils.HashData(utils.SHA256, []byte(fmt.Sprintf("%s-%s", password, StaticHashSalt)))
175
}
176
177
func HashPwd(static string, salt string) string {
178
return utils.HashData(utils.SHA256, []byte(fmt.Sprintf("%s-%s", static, salt)))
179
}
180
181
func TwoHashPwd(password string, salt string) string {
182
return HashPwd(StaticHash(password), salt)
183
}
184
185
func (u *User) WebAuthnID() []byte {
186
bs := make([]byte, 8)
187
binary.LittleEndian.PutUint64(bs, uint64(u.ID))
188
return bs
189
}
190
191
func (u *User) WebAuthnName() string {
192
return u.Username
193
}
194
195
func (u *User) WebAuthnDisplayName() string {
196
return u.Username
197
}
198
199
func (u *User) WebAuthnCredentials() []webauthn.Credential {
200
var res []webauthn.Credential
201
err := json.Unmarshal([]byte(u.Authn), &res)
202
if err != nil {
203
fmt.Println(err)
204
}
205
return res
206
}
207
208
func (u *User) WebAuthnIcon() string {
209
return "https://alistgo.com/logo.svg"
210
}
211
212
// FetchRole is used to load role details by id. It should be set by the op package
213
// to avoid an import cycle between model and op.
214
var FetchRole func(uint) (*Role, error)
215
216
// GetAllBasePathsFromRoles returns all permission paths from user's roles
217
func GetAllBasePathsFromRoles(u *User) []string {
218
basePaths := make([]string, 0)
219
seen := make(map[string]struct{})
220
221
for _, rid := range u.Role {
222
if FetchRole == nil {
223
continue
224
}
225
role, err := FetchRole(uint(rid))
226
if err != nil || role == nil {
227
continue
228
}
229
for _, entry := range role.PermissionScopes {
230
if entry.Path == "" {
231
continue
232
}
233
if _, ok := seen[entry.Path]; !ok {
234
basePaths = append(basePaths, entry.Path)
235
seen[entry.Path] = struct{}{}
236
}
237
}
238
}
239
return basePaths
240
}
241
242