mirror of https://github.com/cloudreve/Cloudreve
595 lines
17 KiB
Go
595 lines
17 KiB
Go
package inventory
|
||
|
||
import (
|
||
"context"
|
||
"crypto/md5"
|
||
"crypto/sha1"
|
||
"crypto/sha256"
|
||
"encoding/base64"
|
||
"encoding/hex"
|
||
"errors"
|
||
"fmt"
|
||
"hash"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/cloudreve/Cloudreve/v4/ent"
|
||
"github.com/cloudreve/Cloudreve/v4/ent/davaccount"
|
||
"github.com/cloudreve/Cloudreve/v4/ent/file"
|
||
"github.com/cloudreve/Cloudreve/v4/ent/passkey"
|
||
"github.com/cloudreve/Cloudreve/v4/ent/schema"
|
||
"github.com/cloudreve/Cloudreve/v4/ent/task"
|
||
"github.com/cloudreve/Cloudreve/v4/ent/user"
|
||
"github.com/cloudreve/Cloudreve/v4/inventory/types"
|
||
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
|
||
"github.com/cloudreve/Cloudreve/v4/pkg/util"
|
||
"github.com/go-webauthn/webauthn/webauthn"
|
||
)
|
||
|
||
type (
|
||
// Ctx keys for eager loading options.
|
||
LoadUserGroup struct{}
|
||
LoadUserPasskey struct{}
|
||
|
||
UserCtx struct{}
|
||
UserIDCtx struct{}
|
||
)
|
||
|
||
var (
|
||
ErrUserEmailExisted = errors.New("user email has been registered")
|
||
ErrInactiveUserExisted = errors.New("email already registered but not activated")
|
||
ErrorUnknownPasswordType = errors.New("unknown password type")
|
||
ErrorIncorrectPassword = errors.New("incorrect password")
|
||
ErrInsufficientPoints = errors.New("insufficient points")
|
||
)
|
||
|
||
type (
|
||
UserClient interface {
|
||
TxOperator
|
||
// New creates a new user. If user email registered, existed User will be returned.
|
||
Create(ctx context.Context, args *NewUserArgs) (*ent.User, error)
|
||
// GetByEmail get the user with given email, user status is ignored.
|
||
GetByEmail(ctx context.Context, email string) (*ent.User, error)
|
||
// GetByID get user by its ID, user status is ignored.
|
||
GetByID(ctx context.Context, id int) (*ent.User, error)
|
||
// GetActiveByID get user by its ID, only active user will be returned.
|
||
GetActiveByID(ctx context.Context, id int) (*ent.User, error)
|
||
// SetStatus Set user to given status
|
||
SetStatus(ctx context.Context, u *ent.User, status user.Status) (*ent.User, error)
|
||
// AnonymousUser returns the anonymous user.
|
||
AnonymousUser(ctx context.Context) (*ent.User, error)
|
||
// GetLoginUserByID returns the login user by its ID. It emits some errors and fallback to anonymous user.
|
||
GetLoginUserByID(ctx context.Context, uid int) (*ent.User, error)
|
||
// GetLoginUserByEmail returns the login user by its WebDAV credentials.
|
||
GetActiveByDavAccount(ctx context.Context, email, pwd string) (*ent.User, error)
|
||
// SaveSettings saves user settings.
|
||
SaveSettings(ctx context.Context, u *ent.User) error
|
||
// SearchActive search active users by Email or nickname.
|
||
SearchActive(ctx context.Context, limit int, keyword string) ([]*ent.User, error)
|
||
// ApplyStorageDiff apply storage diff to user.
|
||
ApplyStorageDiff(ctx context.Context, diffs StorageDiff) error
|
||
// UpdateAvatar updates user avatar.
|
||
UpdateAvatar(ctx context.Context, u *ent.User, avatar string) (*ent.User, error)
|
||
// UpdateNickname updates user nickname.
|
||
UpdateNickname(ctx context.Context, u *ent.User, name string) (*ent.User, error)
|
||
// UpdatePassword updates user password.
|
||
UpdatePassword(ctx context.Context, u *ent.User, newPassword string) (*ent.User, error)
|
||
// UpdateTwoFASecret updates user two factor secret.
|
||
UpdateTwoFASecret(ctx context.Context, u *ent.User, secret string) (*ent.User, error)
|
||
// ListPasskeys list user's passkeys.
|
||
ListPasskeys(ctx context.Context, uid int) ([]*ent.Passkey, error)
|
||
// AddPasskey add passkey to user.
|
||
AddPasskey(ctx context.Context, uid int, name string, credential *webauthn.Credential) (*ent.Passkey, error)
|
||
// RemovePasskey remove passkey from user.
|
||
RemovePasskey(ctx context.Context, uid int, keyId string) error
|
||
// MarkPasskeyUsed updates passkey used at.
|
||
MarkPasskeyUsed(ctx context.Context, uid int, keyId string) error
|
||
// CountByTimeRange count users by time range. Will return all records if start or end is nil.
|
||
CountByTimeRange(ctx context.Context, start, end *time.Time) (int, error)
|
||
// ListUsers list users with pagination.
|
||
ListUsers(ctx context.Context, args *ListUserParameters) (*ListUserResult, error)
|
||
// Upsert upserts a user.
|
||
Upsert(ctx context.Context, u *ent.User, password, twoFa string) (*ent.User, error)
|
||
// Delete deletes a user.
|
||
Delete(ctx context.Context, uid int) error
|
||
// CalculateStorage calculate user's storage from scratch and update user's storage.
|
||
CalculateStorage(ctx context.Context, uid int) (int64, error)
|
||
}
|
||
ListUserParameters struct {
|
||
*PaginationArgs
|
||
GroupID int
|
||
Status user.Status
|
||
Nick string
|
||
Email string
|
||
}
|
||
ListUserResult struct {
|
||
*PaginationResults
|
||
Users []*ent.User
|
||
}
|
||
)
|
||
|
||
func NewUserClient(client *ent.Client) UserClient {
|
||
return &userClient{client: client}
|
||
}
|
||
|
||
type userClient struct {
|
||
client *ent.Client
|
||
}
|
||
|
||
type (
|
||
// NewUserArgs args to create a new user
|
||
NewUserArgs struct {
|
||
Email string
|
||
Nick string // Optional
|
||
PlainPassword string
|
||
Status user.Status
|
||
GroupID int
|
||
Avatar string // Optional
|
||
Language string // Optional
|
||
}
|
||
CreateStoragePackArgs struct {
|
||
UserID int
|
||
Name string
|
||
Size int64
|
||
ExpireAt time.Time
|
||
}
|
||
)
|
||
|
||
func (c *userClient) CountByTimeRange(ctx context.Context, start, end *time.Time) (int, error) {
|
||
if start == nil || end == nil {
|
||
return c.client.User.Query().Count(ctx)
|
||
}
|
||
return c.client.User.Query().Where(user.CreatedAtGTE(*start), user.CreatedAtLT(*end)).Count(ctx)
|
||
}
|
||
|
||
func (c *userClient) UpdateNickname(ctx context.Context, u *ent.User, name string) (*ent.User, error) {
|
||
return c.client.User.UpdateOne(u).SetNick(name).Save(ctx)
|
||
}
|
||
|
||
func (c *userClient) UpdateAvatar(ctx context.Context, u *ent.User, avatar string) (*ent.User, error) {
|
||
return c.client.User.UpdateOne(u).SetAvatar(avatar).Save(ctx)
|
||
}
|
||
|
||
func (c *userClient) UpdateTwoFASecret(ctx context.Context, u *ent.User, secret string) (*ent.User, error) {
|
||
if secret == "" {
|
||
return c.client.User.UpdateOne(u).ClearTwoFactorSecret().Save(ctx)
|
||
}
|
||
return c.client.User.UpdateOne(u).SetTwoFactorSecret(secret).Save(ctx)
|
||
}
|
||
|
||
func (c *userClient) UpdatePassword(ctx context.Context, u *ent.User, newPassword string) (*ent.User, error) {
|
||
digest, err := digestPassword(newPassword)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return c.client.User.UpdateOne(u).SetPassword(digest).Save(ctx)
|
||
}
|
||
|
||
func (c *userClient) SetClient(newClient *ent.Client) TxOperator {
|
||
return &userClient{client: newClient}
|
||
}
|
||
|
||
func (c *userClient) GetClient() *ent.Client {
|
||
return c.client
|
||
}
|
||
|
||
func (c *userClient) ListPasskeys(ctx context.Context, uid int) ([]*ent.Passkey, error) {
|
||
return c.client.Passkey.Query().Where(passkey.UserID(uid)).All(ctx)
|
||
}
|
||
|
||
func (c *userClient) AddPasskey(ctx context.Context, uid int, name string, credential *webauthn.Credential) (*ent.Passkey, error) {
|
||
return c.client.Passkey.Create().
|
||
SetName(name).
|
||
SetCredentialID(base64.StdEncoding.EncodeToString(credential.ID)).
|
||
SetUserID(uid).
|
||
SetCredential(credential).
|
||
Save(ctx)
|
||
}
|
||
|
||
func (c *userClient) RemovePasskey(ctx context.Context, uid int, keyId string) error {
|
||
ctx = schema.SkipSoftDelete(ctx)
|
||
_, err := c.client.Passkey.Delete().Where(passkey.UserID(uid), passkey.CredentialID(keyId)).Exec(ctx)
|
||
return err
|
||
}
|
||
|
||
func (c *userClient) MarkPasskeyUsed(ctx context.Context, uid int, keyId string) error {
|
||
_, err := c.client.Passkey.Update().Where(passkey.UserID(uid), passkey.CredentialID(keyId)).SetUsedAt(time.Now()).Save(ctx)
|
||
return err
|
||
}
|
||
|
||
func (c *userClient) Delete(ctx context.Context, uid int) error {
|
||
// Dav accounts
|
||
if _, err := c.client.DavAccount.Delete().Where(davaccount.OwnerID(uid)).Exec(schema.SkipSoftDelete(ctx)); err != nil {
|
||
return fmt.Errorf("failed to delete dav accounts: %w", err)
|
||
}
|
||
|
||
// Passkeys
|
||
if _, err := c.client.Passkey.Delete().Where(passkey.UserID(uid)).Exec(schema.SkipSoftDelete(ctx)); err != nil {
|
||
return fmt.Errorf("failed to delete passkeys: %w", err)
|
||
}
|
||
|
||
// Tasks
|
||
if _, err := c.client.Task.Delete().Where(task.UserTasks(uid)).Exec(ctx); err != nil {
|
||
return fmt.Errorf("failed to delete tasks: %w", err)
|
||
}
|
||
|
||
return c.client.User.DeleteOneID(uid).Exec(schema.SkipSoftDelete(ctx))
|
||
}
|
||
|
||
func (c *userClient) ApplyStorageDiff(ctx context.Context, diffs StorageDiff) error {
|
||
ae := serializer.NewAggregateError()
|
||
for uid, diff := range diffs {
|
||
if err := c.client.User.Update().Where(user.ID(uid)).AddStorage(diff).Exec(ctx); err != nil {
|
||
ae.Add(fmt.Sprintf("%d", uid), fmt.Errorf("failed to apply storage diff for user %d: %w", uid, err))
|
||
}
|
||
}
|
||
|
||
return ae.Aggregate()
|
||
}
|
||
|
||
func (c *userClient) CalculateStorage(ctx context.Context, uid int) (int64, error) {
|
||
var sum int64
|
||
batchSize := 5000
|
||
offset := 0
|
||
|
||
for {
|
||
allFiles, err := c.client.File.Query().
|
||
Where(file.HasOwnerWith(user.ID(uid))).
|
||
WithEntities().
|
||
Offset(offset).
|
||
Limit(batchSize).
|
||
All(ctx)
|
||
if err != nil {
|
||
return 0, fmt.Errorf("failed to list user files: %w", err)
|
||
}
|
||
|
||
if len(allFiles) == 0 {
|
||
break
|
||
}
|
||
|
||
for _, file := range allFiles {
|
||
for _, entity := range file.Edges.Entities {
|
||
sum += entity.Size
|
||
}
|
||
}
|
||
|
||
offset += batchSize
|
||
}
|
||
|
||
if _, err := c.client.User.UpdateOneID(uid).SetStorage(sum).Save(ctx); err != nil {
|
||
return 0, err
|
||
}
|
||
|
||
return sum, nil
|
||
}
|
||
|
||
func (c *userClient) SetStatus(ctx context.Context, u *ent.User, status user.Status) (*ent.User, error) {
|
||
return c.client.User.UpdateOne(u).SetStatus(status).Save(ctx)
|
||
}
|
||
|
||
func (c *userClient) Create(ctx context.Context, args *NewUserArgs) (*ent.User, error) {
|
||
// Try to check if there's user with same email.
|
||
if existedUser, err := c.GetByEmail(ctx, args.Email); err == nil {
|
||
if existedUser.Status == user.StatusInactive {
|
||
return existedUser, ErrInactiveUserExisted
|
||
}
|
||
return existedUser, ErrUserEmailExisted
|
||
}
|
||
|
||
nick := args.Nick
|
||
if nick == "" {
|
||
nick = strings.Split(args.Email, "@")[0]
|
||
}
|
||
|
||
userSetting := &types.UserSetting{VersionRetention: true, VersionRetentionMax: 10}
|
||
query := c.client.User.Create().
|
||
SetEmail(args.Email).
|
||
SetNick(nick).
|
||
SetStatus(args.Status).
|
||
SetGroupID(args.GroupID).
|
||
SetAvatar(args.Avatar)
|
||
|
||
if args.PlainPassword != "" {
|
||
pwdDigest, err := digestPassword(args.PlainPassword)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to sha256 password: %w", err)
|
||
}
|
||
query.SetPassword(pwdDigest)
|
||
}
|
||
|
||
if args.Language != "" {
|
||
userSetting.Language = args.Language
|
||
}
|
||
query.SetSettings(userSetting)
|
||
|
||
// Create user
|
||
newUser, err := query.
|
||
Save(ctx)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create user: %w", err)
|
||
}
|
||
|
||
if newUser.ID == 1 {
|
||
// For the first user registered, elevate it to admin group.
|
||
if _, err := newUser.Update().SetGroupID(1).Save(ctx); err != nil {
|
||
return newUser, fmt.Errorf("failed to elevate user to admin: %w", err)
|
||
}
|
||
}
|
||
return newUser, nil
|
||
}
|
||
|
||
func (c *userClient) GetByEmail(ctx context.Context, email string) (*ent.User, error) {
|
||
return withUserEagerLoading(ctx, c.client.User.Query().Where(user.EmailEqualFold(email))).First(ctx)
|
||
}
|
||
|
||
func (c *userClient) GetByID(ctx context.Context, id int) (*ent.User, error) {
|
||
return withUserEagerLoading(ctx, c.client.User.Query().Where(user.ID(id))).First(ctx)
|
||
}
|
||
|
||
func (c *userClient) GetActiveByID(ctx context.Context, id int) (*ent.User, error) {
|
||
return withUserEagerLoading(
|
||
ctx,
|
||
c.client.User.Query().
|
||
Where(user.ID(id)).
|
||
Where(user.StatusEQ(user.StatusActive)),
|
||
).First(ctx)
|
||
}
|
||
|
||
func (c *userClient) GetActiveByDavAccount(ctx context.Context, email, pwd string) (*ent.User, error) {
|
||
ctx = context.WithValue(ctx, LoadUserGroup{}, true)
|
||
return withUserEagerLoading(
|
||
ctx,
|
||
c.client.User.Query().
|
||
Where(user.EmailEqualFold(email)).
|
||
Where(user.StatusEQ(user.StatusActive)).
|
||
WithDavAccounts(func(q *ent.DavAccountQuery) {
|
||
q.Where(davaccount.Password(pwd))
|
||
}),
|
||
).First(ctx)
|
||
}
|
||
|
||
func (c *userClient) GetLoginUserByID(ctx context.Context, uid int) (*ent.User, error) {
|
||
ctx = context.WithValue(ctx, LoadUserGroup{}, true)
|
||
if uid > 0 {
|
||
expectedUser, err := c.GetActiveByID(ctx, uid)
|
||
if err == nil {
|
||
return expectedUser, nil
|
||
}
|
||
|
||
return nil, fmt.Errorf("failed to get user by id: %w", err)
|
||
}
|
||
|
||
anonymous, err := c.AnonymousUser(ctx)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to construct anonymous user: %w", err)
|
||
}
|
||
|
||
return anonymous, nil
|
||
}
|
||
|
||
func (c *userClient) SearchActive(ctx context.Context, limit int, keyword string) ([]*ent.User, error) {
|
||
ctx = context.WithValue(ctx, LoadUserGroup{}, true)
|
||
return withUserEagerLoading(
|
||
ctx,
|
||
c.client.User.Query().
|
||
Where(user.Or(user.EmailContainsFold(keyword), user.NickContainsFold(keyword))).
|
||
Limit(limit),
|
||
).All(ctx)
|
||
}
|
||
|
||
func (c *userClient) SaveSettings(ctx context.Context, u *ent.User) error {
|
||
return c.client.User.UpdateOne(u).SetSettings(u.Settings).Exec(ctx)
|
||
}
|
||
|
||
// UserFromContext get user from context
|
||
func UserFromContext(ctx context.Context) *ent.User {
|
||
u, _ := ctx.Value(UserCtx{}).(*ent.User)
|
||
return u
|
||
}
|
||
|
||
// UserIDFromContext get user id from context.
|
||
func UserIDFromContext(ctx context.Context) int {
|
||
uid, ok := ctx.Value(UserIDCtx{}).(int)
|
||
if !ok {
|
||
u := UserFromContext(ctx)
|
||
if u != nil {
|
||
uid = u.ID
|
||
}
|
||
}
|
||
|
||
return uid
|
||
}
|
||
|
||
func (c *userClient) AnonymousUser(ctx context.Context) (*ent.User, error) {
|
||
groupClient := NewGroupClient(c.client, "", nil)
|
||
anonymousGroup, err := groupClient.AnonymousGroup(ctx)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("anyonymous group not found: %w", err)
|
||
}
|
||
|
||
// TODO: save into cache
|
||
anonymous := &ent.User{
|
||
Settings: &types.UserSetting{},
|
||
}
|
||
anonymous.SetGroup(anonymousGroup)
|
||
return anonymous, nil
|
||
}
|
||
|
||
func (c *userClient) ListUsers(ctx context.Context, args *ListUserParameters) (*ListUserResult, error) {
|
||
query := c.client.User.Query()
|
||
if args.GroupID != 0 {
|
||
query = query.Where(user.GroupUsers(args.GroupID))
|
||
}
|
||
if args.Status != "" {
|
||
query = query.Where(user.StatusEQ(args.Status))
|
||
}
|
||
if args.Nick != "" {
|
||
query = query.Where(user.NickContainsFold(args.Nick))
|
||
}
|
||
if args.Email != "" {
|
||
query = query.Where(user.EmailContainsFold(args.Email))
|
||
}
|
||
query.Order(getUserOrderOption(args)...)
|
||
|
||
// Count total items
|
||
total, err := query.Clone().Count(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
users, err := withUserEagerLoading(ctx, query).Limit(args.PageSize).Offset(args.Page * args.PageSize).All(ctx)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &ListUserResult{
|
||
PaginationResults: &PaginationResults{
|
||
TotalItems: total,
|
||
Page: args.Page,
|
||
PageSize: args.PageSize,
|
||
},
|
||
Users: users,
|
||
}, nil
|
||
}
|
||
|
||
func (c *userClient) Upsert(ctx context.Context, u *ent.User, password, twoFa string) (*ent.User, error) {
|
||
if u.ID == 0 {
|
||
q := c.client.User.Create().
|
||
SetEmail(u.Email).
|
||
SetNick(u.Nick).
|
||
SetAvatar(u.Avatar).
|
||
SetStatus(u.Status).
|
||
SetGroupID(u.GroupUsers).
|
||
SetPassword(u.Password).
|
||
SetSettings(&types.UserSetting{})
|
||
|
||
if password != "" {
|
||
pwdDigest, err := digestPassword(password)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to sha256 password: %w", err)
|
||
}
|
||
q.SetPassword(pwdDigest)
|
||
}
|
||
|
||
return q.Save(ctx)
|
||
}
|
||
|
||
q := c.client.User.UpdateOne(u).
|
||
SetEmail(u.Email).
|
||
SetNick(u.Nick).
|
||
SetAvatar(u.Avatar).
|
||
SetStatus(u.Status).
|
||
SetGroupID(u.GroupUsers)
|
||
|
||
if password != "" {
|
||
pwdDigest, err := digestPassword(password)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to sha256 password: %w", err)
|
||
}
|
||
q.SetPassword(pwdDigest)
|
||
}
|
||
|
||
if twoFa != "" {
|
||
q.ClearTwoFactorSecret()
|
||
}
|
||
|
||
return q.Save(ctx)
|
||
}
|
||
|
||
func getUserOrderOption(args *ListUserParameters) []user.OrderOption {
|
||
orderTerm := getOrderTerm(args.Order)
|
||
switch args.OrderBy {
|
||
case user.FieldNick:
|
||
return []user.OrderOption{user.ByNick(orderTerm), user.ByID(orderTerm)}
|
||
case user.FieldStorage:
|
||
return []user.OrderOption{user.ByStorage(orderTerm), user.ByID(orderTerm)}
|
||
case user.FieldEmail:
|
||
return []user.OrderOption{user.ByEmail(orderTerm), user.ByID(orderTerm)}
|
||
case user.FieldUpdatedAt:
|
||
return []user.OrderOption{user.ByUpdatedAt(orderTerm), user.ByID(orderTerm)}
|
||
default:
|
||
return []user.OrderOption{user.ByID(orderTerm)}
|
||
}
|
||
}
|
||
|
||
// IsAnonymousUser check if given user is anonymous user.
|
||
func IsAnonymousUser(u *ent.User) bool {
|
||
return u.ID == 0
|
||
}
|
||
|
||
// CheckPassword 根据明文校验密码
|
||
func CheckPassword(u *ent.User, password string) error {
|
||
// 根据存储密码拆分为 Salt 和 Digest
|
||
passwordStore := strings.Split(u.Password, ":")
|
||
if len(passwordStore) != 2 && len(passwordStore) != 3 {
|
||
return ErrorUnknownPasswordType
|
||
}
|
||
|
||
// 兼容V2密码,升级后存储格式为: md5:$HASH:$SALT
|
||
if len(passwordStore) == 3 {
|
||
if passwordStore[0] != "md5" {
|
||
return ErrorUnknownPasswordType
|
||
}
|
||
hash := md5.New()
|
||
_, err := hash.Write([]byte(passwordStore[2] + password))
|
||
bs := hex.EncodeToString(hash.Sum(nil))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if bs != passwordStore[1] {
|
||
return ErrorIncorrectPassword
|
||
}
|
||
}
|
||
|
||
//计算 Salt 和密码组合的SHA1摘要
|
||
var hasher hash.Hash
|
||
if len(passwordStore[1]) == 64 {
|
||
hasher = sha256.New()
|
||
} else {
|
||
// Compatible with V3
|
||
hasher = sha1.New()
|
||
}
|
||
|
||
_, err := hasher.Write([]byte(password + passwordStore[0]))
|
||
bs := hex.EncodeToString(hasher.Sum(nil))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
if bs != passwordStore[1] {
|
||
return ErrorIncorrectPassword
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func withUserEagerLoading(ctx context.Context, q *ent.UserQuery) *ent.UserQuery {
|
||
if v, ok := ctx.Value(LoadUserGroup{}).(bool); ok && v {
|
||
q.WithGroup(func(gq *ent.GroupQuery) {
|
||
withGroupEagerLoading(ctx, gq)
|
||
})
|
||
}
|
||
if v, ok := ctx.Value(LoadUserPasskey{}).(bool); ok && v {
|
||
q.WithPasskey()
|
||
}
|
||
return q
|
||
}
|
||
|
||
func digestPassword(password string) (string, error) {
|
||
//生成16位 Salt
|
||
salt := util.RandStringRunes(16)
|
||
|
||
//计算 Salt 和密码组合的SHA1摘要
|
||
hash := sha256.New()
|
||
_, err := hash.Write([]byte(password + salt))
|
||
bs := hex.EncodeToString(hash.Sum(nil))
|
||
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
//存储 Salt 值和摘要, ":"分割
|
||
return salt + ":" + string(bs), nil
|
||
}
|