fix(mysql): adapt tenant shared database fields
This commit is contained in:
+268
-46
@@ -45,6 +45,7 @@ type adapter struct {
|
||||
baseConfig *configType
|
||||
tenantDBRouting bool
|
||||
tenantScopedDB bool
|
||||
tenantID t.TenantID
|
||||
tenantRegistryDB string
|
||||
tenantAdapters map[t.TenantID]*adapter
|
||||
tenantMu sync.Mutex
|
||||
@@ -250,6 +251,7 @@ func (a *adapter) Open(jsonconfig json.RawMessage) error {
|
||||
config.Config.DBName = a.dbName
|
||||
a.baseConfig = &config
|
||||
a.tenantDBRouting = config.TenantDBRouting
|
||||
a.tenantID = defaultTenantID
|
||||
a.tenantRegistryDB = strings.TrimSpace(config.TenantRegistryDB)
|
||||
a.tenantAdapters = make(map[t.TenantID]*adapter)
|
||||
|
||||
@@ -296,7 +298,7 @@ func (a *adapter) Close() error {
|
||||
var err error
|
||||
a.tenantMu.Lock()
|
||||
for tenantID, tenantAdp := range a.tenantAdapters {
|
||||
if tenantAdp != nil && tenantAdp.db != nil {
|
||||
if tenantAdp != nil && tenantAdp.db != nil && tenantAdp.db != a.db {
|
||||
if closeErr := tenantAdp.db.Close(); err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
@@ -471,8 +473,27 @@ func (a *adapter) TenantListActive(limit int) ([]t.Tenant, error) {
|
||||
|
||||
func (a *adapter) TenantAdapterFor(tenantID t.TenantID) (dbadapter.TenantBusinessAdapter, dbadapter.TenantAuthAdapter, error) {
|
||||
if !a.tenantDBRouting {
|
||||
if a.tenantID != defaultTenantID && a.tenantID != tenantID {
|
||||
return nil, nil, t.ErrMalformed
|
||||
}
|
||||
if a.registryDB != nil {
|
||||
tenant, err := a.tenantByID(tenantID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if tenant.State != t.TenantStateActive {
|
||||
return nil, nil, t.ErrPermissionDenied
|
||||
}
|
||||
}
|
||||
return a, a, nil
|
||||
}
|
||||
tenant, err := a.tenantByID(tenantID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if tenant.State != t.TenantStateActive {
|
||||
return nil, nil, t.ErrPermissionDenied
|
||||
}
|
||||
a.tenantMu.Lock()
|
||||
if tenantAdp := a.tenantAdapters[tenantID]; tenantAdp != nil {
|
||||
a.tenantMu.Unlock()
|
||||
@@ -480,36 +501,15 @@ func (a *adapter) TenantAdapterFor(tenantID t.TenantID) (dbadapter.TenantBusines
|
||||
}
|
||||
a.tenantMu.Unlock()
|
||||
|
||||
tenant, err := a.tenantByID(tenantID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
dbName := strings.TrimSpace(tenant.DBName)
|
||||
dsn := strings.TrimSpace(tenant.DBDSN)
|
||||
if a.baseConfig == nil {
|
||||
return nil, nil, t.ErrFailed
|
||||
}
|
||||
db, resolvedDBName, err := openMySQLDB(*a.baseConfig, mysqlDefaultString(dsn, a.dsn), dbName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err = db.Ping(); err != nil {
|
||||
db.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
if err = ensureUserPrivacySchema(db); err != nil {
|
||||
db.Close()
|
||||
return nil, nil, errors.New("mysql adapter failed to ensure user privacy schema for tenant: " + err.Error())
|
||||
}
|
||||
registryDB := a.registryDB
|
||||
if registryDB == nil {
|
||||
registryDB = a.db
|
||||
}
|
||||
tenantAdp := &adapter{
|
||||
db: db,
|
||||
db: a.db,
|
||||
registryDB: registryDB,
|
||||
dsn: mysqlDefaultString(dsn, a.dsn),
|
||||
dbName: resolvedDBName,
|
||||
dsn: a.dsn,
|
||||
dbName: a.dbName,
|
||||
maxResults: a.maxResults,
|
||||
maxMessageResults: a.maxMessageResults,
|
||||
version: a.version,
|
||||
@@ -517,12 +517,14 @@ func (a *adapter) TenantAdapterFor(tenantID t.TenantID) (dbadapter.TenantBusines
|
||||
txTimeout: a.txTimeout,
|
||||
baseConfig: a.baseConfig,
|
||||
tenantDBRouting: false,
|
||||
tenantScopedDB: true,
|
||||
tenantID: tenantID,
|
||||
// A dedicated database is still tenant-owned. Keep tenant predicates and
|
||||
// explicit tenant_id writes enabled even though databases are not shared.
|
||||
tenantScopedDB: false,
|
||||
}
|
||||
a.tenantMu.Lock()
|
||||
if existing := a.tenantAdapters[tenantID]; existing != nil {
|
||||
a.tenantMu.Unlock()
|
||||
db.Close()
|
||||
return existing, existing, nil
|
||||
}
|
||||
a.tenantAdapters[tenantID] = tenantAdp
|
||||
@@ -570,7 +572,7 @@ func createTenantRegistry(tx *sql.Tx, registryDB, businessDB string) error {
|
||||
}
|
||||
if _, err := tx.Exec(
|
||||
`CREATE TABLE IF NOT EXISTS im_tenant(
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
id BIGINT NOT NULL,
|
||||
code VARCHAR(64) NOT NULL,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
tenant_desc VARCHAR(256),
|
||||
@@ -1429,18 +1431,35 @@ func (a *adapter) TenantAuthAddRecord(tenantID t.TenantID, uid t.Uid,
|
||||
if tenantAdp != a {
|
||||
return tenantAdp.TenantAuthAddRecord(tenantID, uid, authLvl, secret, expires)
|
||||
}
|
||||
var exp *time.Time
|
||||
if !expires.IsZero() {
|
||||
exp = &expires
|
||||
}
|
||||
var username string
|
||||
ctx, cancel := a.getContext()
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
if err = a.db.GetContext(ctx, &username, "SELECT username FROM im_users WHERE id=?", store.DecodeUid(uid)); err != nil {
|
||||
if err = a.db.GetContext(ctx, &username, "SELECT username FROM im_users WHERE tenant_id=? AND id=?", tenantID, store.DecodeUid(uid)); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return t.ErrNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
return a.AuthAddRecord(uid, "basic", username, authLvl, secret, expires)
|
||||
authID, err := nextSnowflakeID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = a.db.ExecContext(ctx, "INSERT INTO im_auth(tenant_id,id,userid,authLvl,secret,expires) VALUES(?,?,?,?,?,?)",
|
||||
tenantID, authID, store.DecodeUid(uid), authLvl, secret, exp)
|
||||
if isIDOutOfRange(err) {
|
||||
_, err = a.db.ExecContext(ctx, "INSERT INTO im_auth(tenant_id,userid,authLvl,secret,expires) VALUES(?,?,?,?,?)",
|
||||
tenantID, store.DecodeUid(uid), authLvl, secret, exp)
|
||||
}
|
||||
if isDupe(err) {
|
||||
return t.ErrDuplicate
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// TenantAuthDeleteRecord deletes the Basic authentication record.
|
||||
@@ -1452,7 +1471,12 @@ func (a *adapter) TenantAuthDeleteRecord(tenantID t.TenantID, uid t.Uid) error {
|
||||
if tenantAdp != a {
|
||||
return tenantAdp.TenantAuthDeleteRecord(tenantID, uid)
|
||||
}
|
||||
return a.AuthDelScheme(uid, "basic")
|
||||
ctx, cancel := a.getContext()
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
_, err = a.db.ExecContext(ctx, "DELETE FROM im_auth WHERE tenant_id=? AND userid=?", tenantID, store.DecodeUid(uid))
|
||||
return err
|
||||
}
|
||||
|
||||
// TenantAuthUpdateRecord updates the Basic authentication record and optional username.
|
||||
@@ -1465,7 +1489,43 @@ func (a *adapter) TenantAuthUpdateRecord(tenantID t.TenantID, uid t.Uid, usernam
|
||||
if tenantAdp != a {
|
||||
return tenantAdp.TenantAuthUpdateRecord(tenantID, uid, username, authLvl, secret, expires)
|
||||
}
|
||||
return a.AuthUpdRecord(uid, "basic", username, authLvl, secret, expires)
|
||||
params := []string{"authLvl=?"}
|
||||
args := []any{authLvl}
|
||||
if len(secret) > 0 {
|
||||
params = append(params, "secret=?")
|
||||
args = append(args, secret)
|
||||
}
|
||||
if !expires.IsZero() {
|
||||
params = append(params, "expires=?")
|
||||
args = append(args, expires)
|
||||
}
|
||||
decodedUID := store.DecodeUid(uid)
|
||||
ctx, cancel := a.getContextForTx()
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
tx, err := a.db.BeginTxx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
if username != "" {
|
||||
if _, err = tx.ExecContext(ctx, "UPDATE im_users SET username=?,updatedat=? WHERE tenant_id=? AND id=?", username, t.TimeNow(), tenantID, decodedUID); err != nil {
|
||||
if isDupe(err) {
|
||||
return t.ErrDuplicate
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
args = append(args, tenantID, decodedUID)
|
||||
resp, err := tx.ExecContext(ctx, "UPDATE im_auth SET "+strings.Join(params, ",")+" WHERE tenant_id=? AND userid=?", args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count, _ := resp.RowsAffected(); count <= 0 {
|
||||
return t.ErrNotFound
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// TenantAuthGetRecord retrieves a user's Basic authentication record.
|
||||
@@ -1479,7 +1539,29 @@ func (a *adapter) TenantAuthGetRecord(tenantID t.TenantID, uid t.Uid) (string,
|
||||
if tenantAdp != a {
|
||||
return tenantAdp.TenantAuthGetRecord(tenantID, uid)
|
||||
}
|
||||
return a.AuthGetRecord(uid, "basic")
|
||||
var record struct {
|
||||
Username string
|
||||
Authlvl auth.Level
|
||||
Secret []byte
|
||||
Expires *time.Time
|
||||
}
|
||||
ctx, cancel := a.getContext()
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
err = a.db.GetContext(ctx, &record, `SELECT u.username,a.secret,a.expires,a.authlvl
|
||||
FROM im_auth AS a JOIN im_users AS u ON u.tenant_id=a.tenant_id AND u.id=a.userid
|
||||
WHERE a.tenant_id=? AND a.userid=?`, tenantID, store.DecodeUid(uid))
|
||||
if err == sql.ErrNoRows {
|
||||
return "", auth.LevelNone, nil, expires, t.ErrNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return "", auth.LevelNone, nil, expires, err
|
||||
}
|
||||
if record.Expires != nil {
|
||||
expires = *record.Expires
|
||||
}
|
||||
return record.Username, record.Authlvl, record.Secret, expires, nil
|
||||
}
|
||||
|
||||
// TenantAuthGetByLogin retrieves a Basic authentication record by tenant-local login.
|
||||
@@ -1493,7 +1575,34 @@ func (a *adapter) TenantAuthGetByLogin(tenantID t.TenantID, login string) (t.Uid
|
||||
if tenantAdp != a {
|
||||
return tenantAdp.TenantAuthGetByLogin(tenantID, login)
|
||||
}
|
||||
return a.AuthGetUniqueRecord(login)
|
||||
var record struct {
|
||||
Userid int64
|
||||
Authlvl auth.Level
|
||||
Secret []byte
|
||||
Expires *time.Time
|
||||
}
|
||||
lookup := login
|
||||
if len(login) == 11 && asciiDigits(login) {
|
||||
lookup = "+86" + login
|
||||
}
|
||||
ctx, cancel := a.getContext()
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
err = a.db.GetContext(ctx, &record, `SELECT a.userid,a.secret,a.expires,a.authlvl
|
||||
FROM im_users AS u JOIN im_auth AS a ON a.tenant_id=u.tenant_id AND a.userid=u.id
|
||||
WHERE u.tenant_id=? AND (u.username=? OR (u.phone IS NOT NULL AND u.phone=?))
|
||||
ORDER BY (u.username=?) DESC LIMIT 1`, tenantID, login, lookup, login)
|
||||
if err == sql.ErrNoRows {
|
||||
return t.ZeroUid, auth.LevelNone, nil, expires, nil
|
||||
}
|
||||
if err != nil {
|
||||
return t.ZeroUid, auth.LevelNone, nil, expires, err
|
||||
}
|
||||
if record.Expires != nil {
|
||||
expires = *record.Expires
|
||||
}
|
||||
return store.EncodeUid(record.Userid), record.Authlvl, record.Secret, expires, nil
|
||||
}
|
||||
|
||||
// Add user's authentication record
|
||||
@@ -2011,9 +2120,9 @@ func (a *adapter) TenantUserGetByCred(tenantID t.TenantID, method, value string)
|
||||
return t.ZeroUid, t.ErrMalformed
|
||||
}
|
||||
if tenantAdp != a {
|
||||
return tenantAdp.UserGetByCred(method, value)
|
||||
return tenantAdp.TenantUserGetByCred(tenantID, method, value)
|
||||
}
|
||||
if !a.tenantScopedDB && method == "tel" {
|
||||
if method == "tel" {
|
||||
ctx, cancel := a.getContext()
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
@@ -2029,7 +2138,20 @@ func (a *adapter) TenantUserGetByCred(tenantID t.TenantID, method, value string)
|
||||
}
|
||||
return store.EncodeUid(decodedUID), nil
|
||||
}
|
||||
return a.UserGetByCred(method, value)
|
||||
ctx, cancel := a.getContext()
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
var decodedUID int64
|
||||
err = a.db.GetContext(ctx, &decodedUID,
|
||||
"SELECT userid FROM im_credentials WHERE tenant_id=? AND synthetic=?", tenantID, method+":"+value)
|
||||
if err == sql.ErrNoRows {
|
||||
return t.ZeroUid, nil
|
||||
}
|
||||
if err != nil {
|
||||
return t.ZeroUid, err
|
||||
}
|
||||
return store.EncodeUid(decodedUID), nil
|
||||
}
|
||||
|
||||
// UserUnreadCount returns the total number of unread messages in all topics with
|
||||
@@ -3855,9 +3977,73 @@ func (a *adapter) TenantCredUpsert(tenantID t.TenantID, cred *t.Credential) (boo
|
||||
return false, t.ErrMalformed
|
||||
}
|
||||
if tenantAdp != a {
|
||||
return tenantAdp.CredUpsert(cred)
|
||||
return tenantAdp.TenantCredUpsert(tenantID, cred)
|
||||
}
|
||||
return a.CredUpsert(cred)
|
||||
if cred.Method == "tel" {
|
||||
cred.Value, err = normalizeUserPhone(cred.Value)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
ctx, cancel := a.getContextForTx()
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
tx, err := a.db.BeginTxx(ctx, nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
now := t.TimeNow()
|
||||
userID := common.DecodeUidString(cred.User)
|
||||
synth := cred.Method + ":" + cred.Value
|
||||
if !cred.Done {
|
||||
var done bool
|
||||
err = tx.Get(&done, "SELECT done FROM im_credentials WHERE tenant_id=? AND synthetic=?", tenantID, synth)
|
||||
if err == nil {
|
||||
return false, t.ErrDuplicate
|
||||
}
|
||||
if err != sql.ErrNoRows {
|
||||
return false, err
|
||||
}
|
||||
synth = cred.User + ":" + synth
|
||||
if _, err = tx.Exec("UPDATE im_credentials SET deletedat=? WHERE tenant_id=? AND userid=? AND method=? AND done=FALSE",
|
||||
now, tenantID, userID, cred.Method); err != nil {
|
||||
return false, err
|
||||
}
|
||||
res, updateErr := tx.Exec("UPDATE im_credentials SET updatedat=?,deletedat=NULL,resp=?,done=FALSE WHERE tenant_id=? AND synthetic=?",
|
||||
cred.UpdatedAt, cred.Resp, tenantID, synth)
|
||||
if updateErr != nil {
|
||||
return false, updateErr
|
||||
}
|
||||
if rows, _ := res.RowsAffected(); rows > 0 {
|
||||
return false, tx.Commit()
|
||||
}
|
||||
} else if _, err = tx.Exec("DELETE FROM im_credentials WHERE tenant_id=? AND synthetic=?", tenantID, cred.User+":"+synth); err != nil {
|
||||
return false, err
|
||||
}
|
||||
credID, err := nextSnowflakeID()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
_, err = tx.Exec(`INSERT INTO im_credentials(tenant_id,id,createdat,updatedat,method,value,synthetic,userid,resp,done)
|
||||
VALUES(?,?,?,?,?,?,?,?,?,?)`, tenantID, credID, cred.CreatedAt, cred.UpdatedAt, cred.Method, cred.Value, synth, userID, cred.Resp, cred.Done)
|
||||
if isIDOutOfRange(err) {
|
||||
_, err = tx.Exec(`INSERT INTO im_credentials(tenant_id,createdat,updatedat,method,value,synthetic,userid,resp,done)
|
||||
VALUES(?,?,?,?,?,?,?,?,?)`, tenantID, cred.CreatedAt, cred.UpdatedAt, cred.Method, cred.Value, synth, userID, cred.Resp, cred.Done)
|
||||
}
|
||||
if err != nil {
|
||||
if isDupe(err) {
|
||||
return true, t.ErrDuplicate
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
if cred.Done && cred.Method == "tel" {
|
||||
if _, err = tx.Exec("UPDATE im_users SET phone=? WHERE tenant_id=? AND id=? AND (phone IS NULL OR phone='')", cred.Value, tenantID, userID); err != nil {
|
||||
return true, err
|
||||
}
|
||||
}
|
||||
return true, tx.Commit()
|
||||
}
|
||||
|
||||
func tenantCredDel(tx *sqlx.Tx, tenantID t.TenantID, uid t.Uid, method, value string) error {
|
||||
@@ -3989,7 +4175,7 @@ func (a *adapter) TenantCredDel(tenantID t.TenantID, uid t.Uid, method, value st
|
||||
return t.ErrMalformed
|
||||
}
|
||||
if tenantAdp != a {
|
||||
return tenantAdp.CredDel(uid, method, value)
|
||||
return tenantAdp.TenantCredDel(tenantID, uid, method, value)
|
||||
}
|
||||
ctx, cancel := a.getContextForTx()
|
||||
if cancel != nil {
|
||||
@@ -4036,7 +4222,7 @@ func (a *adapter) TenantCredConfirm(tenantID t.TenantID, uid t.Uid, method strin
|
||||
return t.ErrMalformed
|
||||
}
|
||||
if tenantAdp != a {
|
||||
return tenantAdp.CredConfirm(uid, method)
|
||||
return tenantAdp.TenantCredConfirm(tenantID, uid, method)
|
||||
}
|
||||
ctx, cancel := a.getContext()
|
||||
if cancel != nil {
|
||||
@@ -4076,7 +4262,7 @@ func (a *adapter) TenantCredFail(tenantID t.TenantID, uid t.Uid, method string)
|
||||
return t.ErrMalformed
|
||||
}
|
||||
if tenantAdp != a {
|
||||
return tenantAdp.CredFail(uid, method)
|
||||
return tenantAdp.TenantCredFail(tenantID, uid, method)
|
||||
}
|
||||
ctx, cancel := a.getContext()
|
||||
if cancel != nil {
|
||||
@@ -4116,9 +4302,24 @@ func (a *adapter) TenantCredGetActive(tenantID t.TenantID, uid t.Uid, method str
|
||||
return nil, t.ErrMalformed
|
||||
}
|
||||
if tenantAdp != a {
|
||||
return tenantAdp.CredGetActive(uid, method)
|
||||
return tenantAdp.TenantCredGetActive(tenantID, uid, method)
|
||||
}
|
||||
return a.CredGetActive(uid, method)
|
||||
ctx, cancel := a.getContext()
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
var cred t.Credential
|
||||
err = a.db.GetContext(ctx, &cred, `SELECT createdat,updatedat,method,value,resp,done,retries
|
||||
FROM im_credentials WHERE tenant_id=? AND userid=? AND deletedat IS NULL AND method=? AND done=FALSE`,
|
||||
tenantID, store.DecodeUid(uid), method)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cred.User = uid.String()
|
||||
return &cred, nil
|
||||
}
|
||||
|
||||
// CredGetAll returns credential records for the given user and method, all or validated only.
|
||||
@@ -4159,9 +4360,30 @@ func (a *adapter) TenantCredGetAll(tenantID t.TenantID, uid t.Uid, method string
|
||||
return nil, t.ErrMalformed
|
||||
}
|
||||
if tenantAdp != a {
|
||||
return tenantAdp.CredGetAll(uid, method, validatedOnly)
|
||||
return tenantAdp.TenantCredGetAll(tenantID, uid, method, validatedOnly)
|
||||
}
|
||||
return a.CredGetAll(uid, method, validatedOnly)
|
||||
query := `SELECT createdat,updatedat,method,value,resp,done,retries
|
||||
FROM im_credentials WHERE tenant_id=? AND userid=? AND deletedat IS NULL`
|
||||
args := []any{tenantID, store.DecodeUid(uid)}
|
||||
if method != "" {
|
||||
query += " AND method=?"
|
||||
args = append(args, method)
|
||||
}
|
||||
if validatedOnly {
|
||||
query += " AND done=TRUE"
|
||||
}
|
||||
ctx, cancel := a.getContext()
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
var credentials []t.Credential
|
||||
if err = a.db.SelectContext(ctx, &credentials, query, args...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range credentials {
|
||||
credentials[i].User = uid.String()
|
||||
}
|
||||
return credentials, nil
|
||||
}
|
||||
|
||||
// FileUploads
|
||||
|
||||
Reference in New Issue
Block a user