feat: complete registration invitation and auth redesign

This commit is contained in:
richjohnrambo
2026-08-19 00:30:11 +08:00
parent e063e7879e
commit 219ecadf4c
47 changed files with 9014 additions and 224 deletions
+172 -28
View File
@@ -137,6 +137,24 @@ type tenantsMapper struct{}
// Tenants is the tenant registry access object.
var Tenants TenantsPersistenceInterface
// RegistrationConfigPersistenceInterface exposes global registration policy.
type RegistrationConfigPersistenceInterface interface {
SMSEnabled() (bool, error)
}
type registrationConfigMapper struct{}
// RegistrationConfig reads registration policy from the control database.
var RegistrationConfig RegistrationConfigPersistenceInterface
func (registrationConfigMapper) SMSEnabled() (bool, error) {
configAdp, ok := adp.(adapter.RegistrationConfigAdapter)
if !ok {
return true, types.ErrUnsupported
}
return configAdp.RegistrationSMSEnabled()
}
// GetByCode resolves a tenant by its public enterprise code.
func (tenantsMapper) GetByCode(code string) (*types.Tenant, error) {
tenantAdp, ok := adp.(adapter.TenantAdapter)
@@ -368,13 +386,14 @@ func (s storeObj) DbStats() func() any {
// UsersPersistenceInterface is an interface which defines methods for persistent storage of user records.
type UsersPersistenceInterface interface {
Create(tenantID types.TenantID, user *types.User, private any) (*types.User, error)
GetAuthRecord(tenantID types.TenantID, user types.Uid, scheme string) (string, auth.Level, []byte, time.Time, error)
GetAuthUniqueRecord(tenantID types.TenantID, scheme, unique string) (types.Uid, auth.Level, []byte, time.Time, error)
AddAuthRecord(tenantID types.TenantID, uid types.Uid, authLvl auth.Level, scheme, unique string, secret []byte, expires time.Time) error
UpdateAuthRecord(tenantID types.TenantID, uid types.Uid, authLvl auth.Level, scheme, unique string, secret []byte, expires time.Time) error
DelAuthRecords(tenantID types.TenantID, uid types.Uid, scheme string) error
GetAuthRecord(tenantID types.TenantID, user types.Uid) (string, auth.Level, []byte, time.Time, error)
GetAuthByLogin(tenantID types.TenantID, login string) (types.Uid, auth.Level, []byte, time.Time, error)
AddAuthRecord(tenantID types.TenantID, uid types.Uid, authLvl auth.Level, secret []byte, expires time.Time) error
UpdateAuthRecord(tenantID types.TenantID, uid types.Uid, authLvl auth.Level, username string, secret []byte, expires time.Time) error
DeleteAuthRecord(tenantID types.TenantID, uid types.Uid) error
Get(tenantID types.TenantID, uid types.Uid) (*types.User, error)
GetAll(tenantID types.TenantID, uid ...types.Uid) ([]types.User, error)
GetByPhone(tenantID types.TenantID, phone string) (types.Uid, error)
GetByCred(tenantID types.TenantID, method, value string) (types.Uid, error)
Delete(tenantID types.TenantID, id types.Uid, hard bool) error
UpdateLastSeen(tenantID types.TenantID, uid types.Uid, userAgent string, when time.Time) error
@@ -455,61 +474,50 @@ func (usersMapper) Create(tenantID types.TenantID, user *types.User, private any
// GetAuthRecord takes a user ID and a authentication scheme name, fetches unique scheme-dependent identifier and
// authentication secret.
func (usersMapper) GetAuthRecord(tenantID types.TenantID, user types.Uid, scheme string) (string, auth.Level, []byte, time.Time, error) {
func (usersMapper) GetAuthRecord(tenantID types.TenantID, user types.Uid) (string, auth.Level, []byte, time.Time, error) {
tenantAdp, err := tenantAuthAdapter(tenantID)
if err != nil {
return "", auth.LevelNone, nil, time.Time{}, err
}
unique, authLvl, secret, expires, err := tenantAdp.TenantAuthGetRecord(tenantID, user, scheme)
if err == nil {
parts := strings.Split(unique, ":")
if len(parts) > 1 {
unique = parts[1]
} else {
err = types.ErrInternal
}
}
return unique, authLvl, secret, expires, err
return tenantAdp.TenantAuthGetRecord(tenantID, user)
}
// GetAuthUniqueRecord takes a unique identifier and a authentication scheme name, fetches user ID and
// authentication secret.
func (usersMapper) GetAuthUniqueRecord(tenantID types.TenantID, scheme, unique string) (types.Uid, auth.Level, []byte, time.Time, error) {
// GetAuthByLogin fetches a Basic authentication record by username or phone.
func (usersMapper) GetAuthByLogin(tenantID types.TenantID, login string) (types.Uid, auth.Level, []byte, time.Time, error) {
tenantAdp, err := tenantAuthAdapter(tenantID)
if err != nil {
return types.ZeroUid, auth.LevelNone, nil, time.Time{}, err
}
return tenantAdp.TenantAuthGetUniqueRecord(tenantID, scheme+":"+unique)
return tenantAdp.TenantAuthGetByLogin(tenantID, login)
}
// AddAuthRecord creates a new authentication record for the given user.
func (usersMapper) AddAuthRecord(tenantID types.TenantID, uid types.Uid, authLvl auth.Level, scheme, unique string, secret []byte,
func (usersMapper) AddAuthRecord(tenantID types.TenantID, uid types.Uid, authLvl auth.Level, secret []byte,
expires time.Time) error {
tenantAdp, err := tenantAuthAdapter(tenantID)
if err != nil {
return err
}
return tenantAdp.TenantAuthAddRecord(tenantID, uid, scheme, scheme+":"+unique, authLvl, secret, expires)
return tenantAdp.TenantAuthAddRecord(tenantID, uid, authLvl, secret, expires)
}
// UpdateAuthRecord updates authentication record with a new secret and expiration time.
func (usersMapper) UpdateAuthRecord(tenantID types.TenantID, uid types.Uid, authLvl auth.Level, scheme, unique string,
func (usersMapper) UpdateAuthRecord(tenantID types.TenantID, uid types.Uid, authLvl auth.Level, username string,
secret []byte, expires time.Time) error {
tenantAdp, err := tenantAuthAdapter(tenantID)
if err != nil {
return err
}
return tenantAdp.TenantAuthUpdRecord(tenantID, uid, scheme, scheme+":"+unique, authLvl, secret, expires)
return tenantAdp.TenantAuthUpdateRecord(tenantID, uid, username, authLvl, secret, expires)
}
// DelAuthRecords deletes user's auth records of the given scheme.
func (usersMapper) DelAuthRecords(tenantID types.TenantID, uid types.Uid, scheme string) error {
// DeleteAuthRecord deletes the user's Basic authentication record.
func (usersMapper) DeleteAuthRecord(tenantID types.TenantID, uid types.Uid) error {
tenantAdp, err := tenantAuthAdapter(tenantID)
if err != nil {
return err
}
return tenantAdp.TenantAuthDelScheme(tenantID, uid, scheme)
return tenantAdp.TenantAuthDeleteRecord(tenantID, uid)
}
// Get returns a user object for the given user ID or nil if the user is not found.
@@ -552,6 +560,16 @@ func (usersMapper) GetByCred(tenantID types.TenantID, method, value string) (typ
return tenantAdp.TenantUserGetByCred(tenantID, method, value)
}
// GetByPhone resolves an account by the phone identity stored on im_users.
// It intentionally does not require a completed tel credential.
func (usersMapper) GetByPhone(tenantID types.TenantID, phone string) (types.Uid, error) {
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return types.ZeroUid, err
}
return tenantAdp.TenantUserGetByPhone(tenantID, phone)
}
// Delete deletes user records.
func (usersMapper) Delete(tenantID types.TenantID, id types.Uid, hard bool) error {
tenantAdp, err := tenantBusinessAdapter(tenantID)
@@ -3062,6 +3080,25 @@ type verificationMapper struct{}
// Verifications is a singleton for product verification challenges.
var Verifications VerificationPersistenceInterface
// RegistrationInvitePersistenceInterface manages tenant-scoped registration invitations.
type RegistrationInvitePersistenceInterface interface {
Create(tenantID types.TenantID, code *types.RegistrationInviteCode) error
CreateBatch(tenantID types.TenantID, batch *types.RegistrationInviteBatch, codes []types.RegistrationInviteCode) error
GetByCode(tenantID types.TenantID, code string) (*types.RegistrationInviteCode, error)
List(tenantID types.TenantID, opts types.RegistrationInviteListOptions) (*types.RegistrationInvitePage, error)
GetBatch(tenantID types.TenantID, batchID int64) (*types.RegistrationInviteBatch, error)
GetBatchByIdempotency(tenantID types.TenantID, key string) (*types.RegistrationInviteBatch, error)
Revoke(tenantID types.TenantID, codeID int64, now time.Time) error
RevokeBatch(tenantID types.TenantID, batchID int64, now time.Time) (int, error)
RecordUse(tenantID types.TenantID, code string, uid types.Uid, requestID string, now time.Time) error
SyncUsage(tenantID types.TenantID, codeID int64) error
}
type registrationInviteMapper struct{}
// RegistrationInvites is the tenant-scoped registration invitation store.
var RegistrationInvites RegistrationInvitePersistenceInterface
// UserSecurityPersistenceInterface defines user security state used to
// invalidate previously issued login tokens.
type UserSecurityPersistenceInterface interface {
@@ -3267,6 +3304,111 @@ func (verificationMapper) Consume(tenantID types.TenantID, challengeID, tokenHas
return tenantAdp.TenantVerificationConsume(tenantID, challengeID, tokenHash, consumedAt)
}
func tenantRegistrationInviteAdapter(tenantID types.TenantID) (adapter.TenantRegistrationInviteAdapter, error) {
if !tenantID.IsValid() {
return nil, types.ErrMalformed
}
if router, ok := adp.(adapter.TenantRoutedAdapter); ok {
business, _, err := router.TenantAdapterFor(tenantID)
if err != nil {
return nil, err
}
if inviteAdp, ok := business.(adapter.TenantRegistrationInviteAdapter); ok {
return inviteAdp, nil
}
}
if inviteAdp, ok := adp.(adapter.TenantRegistrationInviteAdapter); ok {
return inviteAdp, nil
}
return nil, types.ErrUnsupported
}
func (registrationInviteMapper) Create(tenantID types.TenantID, code *types.RegistrationInviteCode) error {
if code == nil {
return types.ErrMalformed
}
inviteAdp, err := tenantRegistrationInviteAdapter(tenantID)
if err != nil {
return err
}
return inviteAdp.TenantRegistrationInviteCreate(tenantID, code)
}
func (registrationInviteMapper) CreateBatch(tenantID types.TenantID, batch *types.RegistrationInviteBatch, codes []types.RegistrationInviteCode) error {
if batch == nil || len(codes) == 0 {
return types.ErrMalformed
}
inviteAdp, err := tenantRegistrationInviteAdapter(tenantID)
if err != nil {
return err
}
return inviteAdp.TenantRegistrationInviteCreateBatch(tenantID, batch, codes)
}
func (registrationInviteMapper) GetByCode(tenantID types.TenantID, code string) (*types.RegistrationInviteCode, error) {
inviteAdp, err := tenantRegistrationInviteAdapter(tenantID)
if err != nil {
return nil, err
}
return inviteAdp.TenantRegistrationInviteGetByCode(tenantID, code)
}
func (registrationInviteMapper) List(tenantID types.TenantID, opts types.RegistrationInviteListOptions) (*types.RegistrationInvitePage, error) {
inviteAdp, err := tenantRegistrationInviteAdapter(tenantID)
if err != nil {
return nil, err
}
return inviteAdp.TenantRegistrationInviteList(tenantID, opts)
}
func (registrationInviteMapper) GetBatch(tenantID types.TenantID, batchID int64) (*types.RegistrationInviteBatch, error) {
inviteAdp, err := tenantRegistrationInviteAdapter(tenantID)
if err != nil {
return nil, err
}
return inviteAdp.TenantRegistrationInviteBatchGet(tenantID, batchID)
}
func (registrationInviteMapper) GetBatchByIdempotency(tenantID types.TenantID, key string) (*types.RegistrationInviteBatch, error) {
inviteAdp, err := tenantRegistrationInviteAdapter(tenantID)
if err != nil {
return nil, err
}
return inviteAdp.TenantRegistrationInviteBatchGetByIdempotency(tenantID, key)
}
func (registrationInviteMapper) Revoke(tenantID types.TenantID, codeID int64, now time.Time) error {
inviteAdp, err := tenantRegistrationInviteAdapter(tenantID)
if err != nil {
return err
}
return inviteAdp.TenantRegistrationInviteRevoke(tenantID, codeID, now)
}
func (registrationInviteMapper) RevokeBatch(tenantID types.TenantID, batchID int64, now time.Time) (int, error) {
inviteAdp, err := tenantRegistrationInviteAdapter(tenantID)
if err != nil {
return 0, err
}
return inviteAdp.TenantRegistrationInviteBatchRevoke(tenantID, batchID, now)
}
func (registrationInviteMapper) RecordUse(tenantID types.TenantID, code string, uid types.Uid, requestID string, now time.Time) error {
inviteAdp, err := tenantRegistrationInviteAdapter(tenantID)
if err != nil {
return err
}
return inviteAdp.TenantRegistrationInviteRecordUse(tenantID, code, uid, requestID, now)
}
func (registrationInviteMapper) SyncUsage(tenantID types.TenantID, codeID int64) error {
inviteAdp, err := tenantRegistrationInviteAdapter(tenantID)
if err != nil {
return err
}
return inviteAdp.TenantRegistrationInviteSyncUsage(tenantID, codeID)
}
// Get reads a persistent cache entry.
func (pcacheMapper) Get(tenantID types.TenantID, key string) (string, error) {
tenantAdp, err := tenantBusinessAdapter(tenantID)
@@ -3310,6 +3452,7 @@ func SetTestUidGenerator(g types.UidGenerator) {
func init() {
Store = storeObj{}
Tenants = tenantsMapper{}
RegistrationConfig = registrationConfigMapper{}
Users = usersMapper{}
Topics = topicsMapper{}
Subs = subsMapper{}
@@ -3330,6 +3473,7 @@ func init() {
Files = fileMapper{}
PCache = pcacheMapper{}
Verifications = verificationMapper{}
RegistrationInvites = registrationInviteMapper{}
Security = userSecurityMapper{}
Relations = relationsMapper{}
Favorites = favoritesMapper{}