github.com/machinefi/w3bstream@v1.6.5-rc9.0.20240426031326-b8c7c4876e72/pkg/modules/account/account.go (about) 1 package account 2 3 import ( 4 "context" 5 "encoding/hex" 6 7 "github.com/ethereum/go-ethereum/crypto" 8 9 confid "github.com/machinefi/w3bstream/pkg/depends/conf/id" 10 "github.com/machinefi/w3bstream/pkg/depends/kit/logr" 11 "github.com/machinefi/w3bstream/pkg/depends/kit/sqlx" 12 "github.com/machinefi/w3bstream/pkg/depends/kit/statusx" 13 "github.com/machinefi/w3bstream/pkg/depends/util" 14 "github.com/machinefi/w3bstream/pkg/enums" 15 "github.com/machinefi/w3bstream/pkg/errors/status" 16 "github.com/machinefi/w3bstream/pkg/models" 17 "github.com/machinefi/w3bstream/pkg/modules/operator" 18 "github.com/machinefi/w3bstream/pkg/types" 19 ) 20 21 type CreateAccountByUsernameReq struct { 22 Username string `json:"username"` 23 Role enums.AccountRole `json:"role"` 24 AvatarURL string `json:"avatarURL,omitempty" validate:"@url"` 25 Password string `json:"-"` 26 Source enums.AccountSource `json:"-"` 27 } 28 29 type CreateAccountByUsernameRsp struct { 30 *models.Account 31 Password string `json:"password"` 32 } 33 34 func CreateAccountByUsername(ctx context.Context, r *CreateAccountByUsernameReq) (*CreateAccountByUsernameRsp, error) { 35 d := types.MustMgrDBExecutorFromContext(ctx) 36 g := confid.MustSFIDGeneratorFromContext(ctx) 37 38 rel := &models.RelAccount{AccountID: g.MustGenSFID()} 39 if r.Source == 0 { 40 r.Source = enums.ACCOUNT_SOURCE__SUBMIT 41 } 42 acc := (*models.Account)(nil) 43 passwd := r.Password 44 45 err := sqlx.NewTasks(d).With( 46 func(db sqlx.DBExecutor) error { 47 acc = &models.Account{ 48 RelAccount: *rel, 49 AccountInfo: models.AccountInfo{ 50 State: enums.ACCOUNT_STATE__ENABLED, 51 Role: r.Role, 52 Avatar: r.AvatarURL, 53 }, 54 } 55 if err := acc.Create(db); err != nil { 56 if sqlx.DBErr(err).IsConflict() { 57 return status.AccountConflict 58 } 59 return status.DatabaseError.StatusErr().WithDesc(err.Error()) 60 } 61 return nil 62 }, 63 func(db sqlx.DBExecutor) error { 64 if err := (&models.AccountIdentity{ 65 RelAccount: *rel, 66 AccountIdentityInfo: models.AccountIdentityInfo{ 67 Type: enums.ACCOUNT_IDENTITY_TYPE__USERNAME, 68 IdentityID: r.Username, 69 Source: r.Source, 70 }, 71 }).Create(db); err != nil { 72 if sqlx.DBErr(err).IsConflict() { 73 return status.AccountIdentityConflict 74 } 75 return status.DatabaseError.StatusErr().WithDesc(err.Error()) 76 } 77 return nil 78 }, 79 func(db sqlx.DBExecutor) error { 80 if passwd == "" { 81 passwd = string(util.GenRandomPassword(8, 3)) 82 } 83 if err := (&models.AccountPassword{ 84 RelAccount: *rel, 85 RelAccountPassword: models.RelAccountPassword{PasswordID: g.MustGenSFID()}, 86 AccountPasswordData: models.AccountPasswordData{ 87 Type: enums.PASSWORD_TYPE__LOGIN, 88 Password: util.HashOfAccountPassword( 89 rel.AccountID.String(), passwd, 90 ), 91 }, 92 }).Create(db); err != nil { 93 if sqlx.DBErr(err).IsConflict() { 94 return status.AccountPasswordConflict 95 } 96 return status.DatabaseError.StatusErr().WithDesc(err.Error()) 97 } 98 return nil 99 }, 100 func(d sqlx.DBExecutor) error { 101 req := operator.CreateReq{ 102 Name: operator.DefaultOperatorName, 103 PrivateKey: generateRandomPrivateKey(), 104 } 105 ctx := types.WithAccount(types.WithMgrDBExecutor(ctx, d), acc) 106 _, err := operator.Create(ctx, &req) 107 return err 108 }, 109 ).Do() 110 111 if err != nil { 112 return nil, err 113 } 114 return &CreateAccountByUsernameRsp{ 115 Account: acc, 116 Password: passwd, 117 }, nil 118 } 119 120 type UpdatePasswordReq struct { 121 OldPassword string `json:"oldPassword"` 122 Password string `json:"password"` 123 } 124 125 func UpdateAccountPassword(ctx context.Context, accountID types.SFID, r *UpdatePasswordReq) error { 126 d := types.MustMgrDBExecutorFromContext(ctx) 127 128 var ( 129 rel = models.RelAccount{AccountID: accountID} 130 acc *models.Account 131 aci *models.AccountIdentity 132 ap *models.AccountPassword 133 ) 134 135 err := sqlx.NewTasks(d).With( 136 func(db sqlx.DBExecutor) error { 137 acc = &models.Account{RelAccount: rel} 138 if err := acc.FetchByAccountID(db); err != nil { 139 if sqlx.DBErr(err).IsNotFound() { 140 return status.AccountNotFound 141 } 142 return status.DatabaseError.StatusErr().WithDesc(err.Error()) 143 } 144 if acc.State != enums.ACCOUNT_STATE__ENABLED { 145 return status.DisabledAccount 146 } 147 return nil 148 }, 149 func(db sqlx.DBExecutor) error { 150 aci = &models.AccountIdentity{ 151 RelAccount: rel, 152 AccountIdentityInfo: models.AccountIdentityInfo{ 153 Type: enums.ACCOUNT_IDENTITY_TYPE__USERNAME, 154 }, 155 } 156 if err := aci.FetchByAccountIDAndType(db); err != nil { 157 if sqlx.DBErr(err).IsNotFound() { 158 return status.AccountIdentityNotFound 159 } 160 return status.DatabaseError.StatusErr().WithDesc(err.Error()) 161 } 162 return nil 163 }, 164 func(db sqlx.DBExecutor) error { 165 ap = &models.AccountPassword{ 166 RelAccount: rel, 167 AccountPasswordData: models.AccountPasswordData{ 168 Type: enums.PASSWORD_TYPE__LOGIN, 169 }, 170 } 171 if err := ap.FetchByAccountIDAndType(db); err != nil { 172 if sqlx.DBErr(err).IsNotFound() { 173 return status.AccountPasswordNotFound 174 } 175 return status.DatabaseError.StatusErr().WithDesc(err.Error()) 176 } 177 if ap.Password != util.HashOfAccountPassword(accountID.String(), r.OldPassword) { 178 return status.InvalidOldPassword 179 } 180 if r.OldPassword == r.Password { 181 return status.InvalidNewPassword 182 } 183 return nil 184 }, 185 func(db sqlx.DBExecutor) error { 186 ap.Password = util.HashOfAccountPassword(accountID.String(), r.Password) 187 if err := ap.UpdateByAccountIDAndType(db); err != nil { 188 return status.DatabaseError.StatusErr().WithDesc(err.Error()) 189 } 190 return nil 191 }, 192 ).Do() 193 194 if err != nil { 195 return err 196 } 197 return nil 198 } 199 200 type LoginByUsernameReq struct { 201 Username string `json:"username"` 202 Password string `json:"password"` 203 } 204 205 type LoginRsp struct { 206 AccountID types.SFID `json:"accountID"` 207 AccountRole enums.AccountRole `json:"accountRole"` 208 Token string `json:"token"` 209 ExpireAt types.Timestamp `json:"expireAt"` 210 Issuer string `json:"issuer"` 211 } 212 213 func ValidateLoginByUsername(ctx context.Context, r *LoginByUsernameReq) (*models.Account, error) { 214 d := types.MustMgrDBExecutorFromContext(ctx) 215 216 var ( 217 rel models.RelAccount 218 aci *models.AccountIdentity 219 acc *models.Account 220 ) 221 222 err := sqlx.NewTasks(d).With( 223 func(db sqlx.DBExecutor) error { 224 aci = &models.AccountIdentity{ 225 RelAccount: models.RelAccount{}, 226 AccountIdentityInfo: models.AccountIdentityInfo{ 227 Type: enums.ACCOUNT_IDENTITY_TYPE__USERNAME, 228 IdentityID: r.Username, 229 }, 230 } 231 if err := aci.FetchByTypeAndIdentityID(db); err != nil { 232 if sqlx.DBErr(err).IsNotFound() { 233 return status.AccountIdentityNotFound 234 } 235 return status.DatabaseError.StatusErr().WithDesc(err.Error()) 236 } 237 rel.AccountID = aci.AccountID 238 return nil 239 }, 240 func(db sqlx.DBExecutor) error { 241 acc = &models.Account{RelAccount: rel} 242 if err := acc.FetchByAccountID(db); err != nil { 243 if sqlx.DBErr(err).IsNotFound() { 244 return status.AccountNotFound 245 } 246 return status.DatabaseError.StatusErr().WithDesc(err.Error()) 247 } 248 if acc.State != enums.ACCOUNT_STATE__ENABLED { 249 return status.DisabledAccount 250 } 251 return nil 252 }, 253 func(db sqlx.DBExecutor) error { 254 ap := &models.AccountPassword{ 255 RelAccount: rel, 256 AccountPasswordData: models.AccountPasswordData{ 257 Type: enums.PASSWORD_TYPE__LOGIN, 258 }, 259 } 260 if err := ap.FetchByAccountIDAndType(db); err != nil { 261 if sqlx.DBErr(err).IsNotFound() { 262 return status.AccountPasswordNotFound 263 } 264 return status.DatabaseError.StatusErr().WithDesc(err.Error()) 265 } 266 if util.HashOfAccountPassword(acc.AccountID.String(), r.Password) != ap.Password { 267 return status.InvalidPassword 268 } 269 return nil 270 }, 271 ).Do() 272 273 if err != nil { 274 return nil, err 275 } 276 return acc, nil 277 } 278 279 func GetAccountByAccountID(ctx context.Context, accountID types.SFID) (*models.Account, error) { 280 ctx, l := logr.Start(ctx, "account.GetAccountByAccountID") 281 defer l.End() 282 283 d := types.MustMgrDBExecutorFromContext(ctx) 284 m := &models.Account{RelAccount: models.RelAccount{AccountID: accountID}} 285 286 err := m.FetchByAccountID(d) 287 if err != nil { 288 if sqlx.DBErr(err).IsNotFound() { 289 return nil, status.AccountNotFound 290 } 291 return nil, status.DatabaseError.StatusErr().WithDesc(err.Error()) 292 } 293 return m, err 294 } 295 296 func CreateAdminIfNotExist(ctx context.Context) (string, error) { 297 ret, err := CreateAccountByUsername(ctx, &CreateAccountByUsernameReq{ 298 Username: "admin", 299 Role: enums.ACCOUNT_ROLE__ADMIN, 300 Password: "iotex.W3B.admin", 301 Source: enums.ACCOUNT_SOURCE__INIT, 302 }) 303 if err != nil { 304 key := statusx.FromErr(err).Key 305 if key == status.AccountConflict.Key() || 306 key == status.AccountIdentityConflict.Key() { 307 return "", nil 308 } 309 return "", err 310 } 311 return ret.Password, nil 312 } 313 314 func generateRandomPrivateKey() string { 315 priKey, err := crypto.GenerateKey() 316 if err != nil { 317 return "" 318 } 319 return hex.EncodeToString(crypto.FromECDSA(priKey)) 320 }