mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-06-04 13:30:32 +08:00
refactor(weknoracloud): update credential handling and configuration
- Replaced references to Docreader credentials with WeKnoraCloud credentials across multiple services and handlers. - Introduced a new CredentialsConfig structure to manage third-party provider credentials, specifically for WeKnoraCloud. - Updated database schema to include a credentials column for storing WeKnoraCloud AppID and AppSecret. - Enhanced methods for retrieving and utilizing WeKnoraCloud credentials, ensuring proper encryption and decryption during storage and retrieval. This update improves the management of WeKnoraCloud credentials, streamlining the integration and enhancing security measures.
This commit is contained in:
@@ -8057,12 +8057,12 @@ func (s *knowledgeService) resolveDocReader(ctx context.Context, engine, fileTyp
|
||||
case docparser.SimpleEngineName:
|
||||
return &docparser.SimpleFormatReader{}
|
||||
case docparser.WeKnoraCloudEngineName:
|
||||
creds := s.tenantService.GetDocreaderCredentials(ctx)
|
||||
creds := s.tenantService.GetWeKnoraCloudCredentials(ctx)
|
||||
if creds == nil {
|
||||
logger.Warnf(ctx, "[resolveDocReader] WeKnoraCloud: no tenant docreader credentials (fileType=%s)", fileType)
|
||||
logger.Warnf(ctx, "[resolveDocReader] WeKnoraCloud: no tenant credentials (fileType=%s)", fileType)
|
||||
return nil
|
||||
}
|
||||
reader, err := docparser.NewWeKnoraCloudSignedDocumentReader(creds.AppID, creds.APIKey)
|
||||
reader, err := docparser.NewWeKnoraCloudSignedDocumentReader(creds.AppID, creds.AppSecret)
|
||||
if err != nil {
|
||||
logger.Errorf(ctx, "[resolveDocReader] WeKnoraCloud reader init failed: %v", err)
|
||||
return nil
|
||||
|
||||
@@ -71,7 +71,7 @@ func (s *modelService) resolveWeKnoraCloudCredentials(ctx context.Context, param
|
||||
if s.tenantService == nil {
|
||||
return
|
||||
}
|
||||
creds := s.tenantService.GetDocreaderCredentials(ctx)
|
||||
creds := s.tenantService.GetWeKnoraCloudCredentials(ctx)
|
||||
if creds == nil {
|
||||
return
|
||||
}
|
||||
@@ -79,7 +79,7 @@ func (s *modelService) resolveWeKnoraCloudCredentials(ctx context.Context, param
|
||||
appID = creds.AppID
|
||||
}
|
||||
if appSecret == "" {
|
||||
appSecret = creds.APIKey
|
||||
appSecret = creds.AppSecret
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -343,42 +343,24 @@ func (s *tenantService) GetTenantByIDForUser(ctx context.Context, tenantID uint6
|
||||
return tenant, nil
|
||||
}
|
||||
|
||||
func (s *tenantService) GetDocreaderCredentials(ctx context.Context) *types.DocreaderCredentials {
|
||||
// Try to get tenant info directly first
|
||||
func (s *tenantService) GetWeKnoraCloudCredentials(ctx context.Context) *types.WeKnoraCloudCredentials {
|
||||
// Try to get tenant info from context first (already loaded by middleware).
|
||||
// CredentialsConfig.Scan handles decryption, so credentials are ready to use.
|
||||
if tenant, ok := types.TenantInfoFromContext(ctx); ok {
|
||||
if tenant.ParserEngineConfig != nil {
|
||||
appID := strings.TrimSpace(tenant.ParserEngineConfig.DocreaderAppID)
|
||||
if appID == "" || tenant.ParserEngineConfig.DocreaderAPIKey == "" {
|
||||
return nil
|
||||
}
|
||||
if key := utils.GetAESKey(); key != nil {
|
||||
if encrypted, err := utils.DecryptAESGCM(tenant.ParserEngineConfig.DocreaderAPIKey, key); err == nil {
|
||||
return &types.DocreaderCredentials{AppID: appID, APIKey: encrypted}
|
||||
}
|
||||
}
|
||||
return &types.DocreaderCredentials{AppID: appID, APIKey: tenant.ParserEngineConfig.DocreaderAPIKey}
|
||||
if creds := tenant.Credentials.GetWeKnoraCloud(); creds != nil {
|
||||
return creds
|
||||
}
|
||||
}
|
||||
|
||||
// If no tenant info in context, try to get tenant ID and load tenant
|
||||
// Fallback: load tenant from repo by tenantID
|
||||
tenantID, ok := types.TenantIDFromContext(ctx)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load tenant from repo if we only have tenantID
|
||||
tenant, err := s.repo.GetTenantByID(ctx, tenantID)
|
||||
if err == nil && tenant != nil && tenant.ParserEngineConfig != nil {
|
||||
appID := strings.TrimSpace(tenant.ParserEngineConfig.DocreaderAppID)
|
||||
if appID != "" && tenant.ParserEngineConfig.DocreaderAPIKey != "" {
|
||||
if key := utils.GetAESKey(); key != nil {
|
||||
if encrypted, err := utils.DecryptAESGCM(tenant.ParserEngineConfig.DocreaderAPIKey, key); err == nil {
|
||||
return &types.DocreaderCredentials{AppID: appID, APIKey: encrypted}
|
||||
}
|
||||
}
|
||||
return &types.DocreaderCredentials{AppID: appID, APIKey: tenant.ParserEngineConfig.DocreaderAPIKey}
|
||||
}
|
||||
if err != nil || tenant == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
return tenant.Credentials.GetWeKnoraCloud()
|
||||
}
|
||||
|
||||
@@ -46,15 +46,8 @@ func (s *weKnoraCloudService) SaveCredentials(ctx context.Context, appID, appSec
|
||||
return fmt.Errorf("credential verification failed: %w", err)
|
||||
}
|
||||
|
||||
encryptedSecret := appSecret
|
||||
if key := utils.GetAESKey(); key != nil {
|
||||
if encrypted, err := utils.EncryptAESGCM(appSecret, key); err == nil {
|
||||
encryptedSecret = encrypted
|
||||
}
|
||||
}
|
||||
|
||||
tenantID := types.MustTenantIDFromContext(ctx)
|
||||
return s.updateTenantCredentials(ctx, tenantID, appID, encryptedSecret)
|
||||
return s.updateTenantCredentials(ctx, tenantID, appID, appSecret)
|
||||
}
|
||||
|
||||
// verifyCredentials 向 WeKnoraCloud /api/v1/health 发送带签名头的 GET。
|
||||
@@ -105,30 +98,26 @@ func (s *weKnoraCloudService) CheckStatus(ctx context.Context) (*types.WeKnoraCl
|
||||
return &types.WeKnoraCloudStatusResult{HasModels: false, NeedsReinit: false}, nil
|
||||
}
|
||||
|
||||
// Check if tenant has WeKnoraCloud credentials in parser config
|
||||
if tenant.ParserEngineConfig == nil || tenant.ParserEngineConfig.DocreaderAppID == "" || tenant.ParserEngineConfig.DocreaderAPIKey == "" {
|
||||
return &types.WeKnoraCloudStatusResult{
|
||||
HasModels: false,
|
||||
NeedsReinit: false,
|
||||
}, nil
|
||||
creds := tenant.Credentials.GetWeKnoraCloud()
|
||||
if creds == nil {
|
||||
return &types.WeKnoraCloudStatusResult{HasModels: false, NeedsReinit: false}, nil
|
||||
}
|
||||
|
||||
// Try to decrypt the API key
|
||||
if key := utils.GetAESKey(); key != nil {
|
||||
if _, err := utils.DecryptAESGCM(tenant.ParserEngineConfig.DocreaderAPIKey, key); err != nil {
|
||||
return &types.WeKnoraCloudStatusResult{
|
||||
HasModels: true,
|
||||
NeedsReinit: true,
|
||||
Reason: "WeKnoraCloud 凭证解密失败(服务重启后加密密钥已变更),请重新填写 APPID 和 APPSECRET",
|
||||
}, nil
|
||||
}
|
||||
// CredentialsConfig.Scan already attempts decryption.
|
||||
// If the AES key has rotated, Scan silently keeps the enc:v1:... blob.
|
||||
if strings.HasPrefix(creds.AppSecret, utils.EncPrefix) {
|
||||
return &types.WeKnoraCloudStatusResult{
|
||||
HasModels: true,
|
||||
NeedsReinit: true,
|
||||
Reason: "WeKnoraCloud 凭证解密失败(服务重启后加密密钥已变更),请重新填写 APPID 和 APPSECRET",
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &types.WeKnoraCloudStatusResult{HasModels: true, NeedsReinit: false}, nil
|
||||
}
|
||||
|
||||
// updateTenantCredentials 更新租户的 WeKnoraCloud 凭证和 DocReader 地址
|
||||
func (s *weKnoraCloudService) updateTenantCredentials(ctx context.Context, tenantID uint64, appID, encryptedSecret string) error {
|
||||
// updateTenantCredentials 更新租户的 WeKnoraCloud 凭证
|
||||
func (s *weKnoraCloudService) updateTenantCredentials(ctx context.Context, tenantID uint64, appID, appSecret string) error {
|
||||
if s.tenantRepo == nil {
|
||||
return fmt.Errorf("tenant repository is required")
|
||||
}
|
||||
@@ -137,10 +126,12 @@ func (s *weKnoraCloudService) updateTenantCredentials(ctx context.Context, tenan
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tenant.ParserEngineConfig == nil {
|
||||
tenant.ParserEngineConfig = &types.ParserEngineConfig{}
|
||||
if tenant.Credentials == nil {
|
||||
tenant.Credentials = &types.CredentialsConfig{}
|
||||
}
|
||||
tenant.Credentials.WeKnoraCloud = &types.WeKnoraCloudCredentials{
|
||||
AppID: appID,
|
||||
AppSecret: appSecret,
|
||||
}
|
||||
tenant.ParserEngineConfig.DocreaderAppID = appID
|
||||
tenant.ParserEngineConfig.DocreaderAPIKey = encryptedSecret
|
||||
return s.tenantRepo.UpdateTenant(ctx, tenant)
|
||||
}
|
||||
|
||||
@@ -1515,9 +1515,9 @@ func (h *InitializationHandler) CheckRemoteModel(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
var appID, appSecret string
|
||||
if tenantInfo.ParserEngineConfig != nil {
|
||||
appID = tenantInfo.ParserEngineConfig.DocreaderAppID
|
||||
appSecret = tenantInfo.ParserEngineConfig.DocreaderAPIKey
|
||||
if creds := tenantInfo.Credentials.GetWeKnoraCloud(); creds != nil {
|
||||
appID = creds.AppID
|
||||
appSecret = creds.AppSecret
|
||||
}
|
||||
|
||||
// 创建模型配置进行测试
|
||||
@@ -1612,9 +1612,9 @@ func (h *InitializationHandler) TestEmbeddingModel(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
var appID, appSecret string
|
||||
if tenantInfo.ParserEngineConfig != nil {
|
||||
appID = tenantInfo.ParserEngineConfig.DocreaderAppID
|
||||
appSecret = tenantInfo.ParserEngineConfig.DocreaderAPIKey
|
||||
if creds := tenantInfo.Credentials.GetWeKnoraCloud(); creds != nil {
|
||||
appID = creds.AppID
|
||||
appSecret = creds.AppSecret
|
||||
}
|
||||
|
||||
// 构造 embedder 配置
|
||||
@@ -1740,9 +1740,9 @@ func (h *InitializationHandler) checkRerankModelConnection(ctx context.Context,
|
||||
return false, "租户信息未找到"
|
||||
}
|
||||
var appID, appSecret string
|
||||
if tenantInfo.ParserEngineConfig != nil {
|
||||
appID = tenantInfo.ParserEngineConfig.DocreaderAppID
|
||||
appSecret = tenantInfo.ParserEngineConfig.DocreaderAPIKey
|
||||
if creds := tenantInfo.Credentials.GetWeKnoraCloud(); creds != nil {
|
||||
appID = creds.AppID
|
||||
appSecret = creds.AppSecret
|
||||
}
|
||||
config.AppID = appID
|
||||
config.AppSecret = appSecret
|
||||
|
||||
@@ -142,8 +142,16 @@ func (h *SystemHandler) getDocReaderConnInfo() (addr, transport string) {
|
||||
func (h *SystemHandler) ListParserEngines(c *gin.Context) {
|
||||
var overrides map[string]string
|
||||
if v, exists := c.Get(types.TenantInfoContextKey.String()); exists {
|
||||
if tenant, ok := v.(*types.Tenant); ok && tenant != nil && tenant.ParserEngineConfig != nil {
|
||||
overrides = tenant.ParserEngineConfig.ToOverridesMap()
|
||||
if tenant, ok := v.(*types.Tenant); ok && tenant != nil {
|
||||
if tenant.ParserEngineConfig != nil {
|
||||
overrides = tenant.ParserEngineConfig.ToOverridesMap()
|
||||
}
|
||||
if creds := tenant.Credentials.GetWeKnoraCloud(); creds != nil {
|
||||
if overrides == nil {
|
||||
overrides = make(map[string]string)
|
||||
}
|
||||
overrides["weknoracloud_app_id"] = creds.AppID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,8 +204,16 @@ func (h *SystemHandler) ReconnectDocReader(c *gin.Context) {
|
||||
|
||||
var overrides map[string]string
|
||||
if v, exists := c.Get(types.TenantInfoContextKey.String()); exists {
|
||||
if tenant, ok := v.(*types.Tenant); ok && tenant != nil && tenant.ParserEngineConfig != nil {
|
||||
overrides = tenant.ParserEngineConfig.ToOverridesMap()
|
||||
if tenant, ok := v.(*types.Tenant); ok && tenant != nil {
|
||||
if tenant.ParserEngineConfig != nil {
|
||||
overrides = tenant.ParserEngineConfig.ToOverridesMap()
|
||||
}
|
||||
if creds := tenant.Credentials.GetWeKnoraCloud(); creds != nil {
|
||||
if overrides == nil {
|
||||
overrides = make(map[string]string)
|
||||
}
|
||||
overrides["weknoracloud_app_id"] = creds.AppID
|
||||
}
|
||||
}
|
||||
}
|
||||
remoteEngines := h.fetchRemoteEngines(c.Request.Context(), h.documentReader, overrides)
|
||||
@@ -223,6 +239,16 @@ func (h *SystemHandler) CheckParserEngines(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
overrides := body.ToOverridesMap()
|
||||
if v, exists := c.Get(types.TenantInfoContextKey.String()); exists {
|
||||
if tenant, ok := v.(*types.Tenant); ok && tenant != nil {
|
||||
if creds := tenant.Credentials.GetWeKnoraCloud(); creds != nil {
|
||||
if overrides == nil {
|
||||
overrides = make(map[string]string)
|
||||
}
|
||||
overrides["weknoracloud_app_id"] = creds.AppID
|
||||
}
|
||||
}
|
||||
}
|
||||
reader, docreaderAddr, docreaderTransport := h.resolveDocReader(c.Request.Context(), overrides)
|
||||
connected := reader != nil && reader.IsConnected()
|
||||
remoteEngines := h.fetchRemoteEngines(c.Request.Context(), reader, overrides)
|
||||
@@ -1034,11 +1060,11 @@ func (h *SystemHandler) ResolveDocumentReader(ctx context.Context, addr string)
|
||||
}
|
||||
|
||||
if service.IsWeKnoraCloudDocReaderAddr(addr) {
|
||||
creds := h.tenantSvc.GetDocreaderCredentials(ctx)
|
||||
creds := h.tenantSvc.GetWeKnoraCloudCredentials(ctx)
|
||||
if creds == nil {
|
||||
return nil
|
||||
}
|
||||
reader, err := docparser.NewWeKnoraCloudSignedDocumentReader(creds.AppID, creds.APIKey)
|
||||
reader, err := docparser.NewWeKnoraCloudSignedDocumentReader(creds.AppID, creds.AppSecret)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ func (e *weKnoraCloudEngine) FileTypes(_ bool) []string {
|
||||
return []string{"docx", "doc", "pdf", "md", "markdown", "xlsx", "xls", "pptx", "ppt"}
|
||||
}
|
||||
func (e *weKnoraCloudEngine) CheckAvailable(docreaderConnected bool, overrides map[string]string) (bool, string) {
|
||||
if overrides["docreader_app_id"] != "" {
|
||||
if overrides["weknoracloud_app_id"] != "" {
|
||||
return true, ""
|
||||
}
|
||||
return false, "WeKnora Cloud credentials not configured. Go to Settings → WeKnora Cloud to set up."
|
||||
|
||||
@@ -28,8 +28,8 @@ type TenantService interface {
|
||||
SearchTenants(ctx context.Context, keyword string, tenantID uint64, page, pageSize int) ([]*types.Tenant, int64, error)
|
||||
// GetTenantByIDForUser gets a tenant by ID with permission check
|
||||
GetTenantByIDForUser(ctx context.Context, tenantID uint64, userID string) (*types.Tenant, error)
|
||||
// GetTenantByAPIKey gets a tenant by API key
|
||||
GetDocreaderCredentials(ctx context.Context) *types.DocreaderCredentials
|
||||
// GetWeKnoraCloudCredentials returns the decrypted WeKnoraCloud credentials for the current tenant.
|
||||
GetWeKnoraCloudCredentials(ctx context.Context) *types.WeKnoraCloudCredentials
|
||||
}
|
||||
|
||||
// TenantRepository defines the tenant repository interface
|
||||
|
||||
@@ -101,6 +101,8 @@ type Tenant struct {
|
||||
ConversationConfig *ConversationConfig `yaml:"conversation_config" json:"conversation_config" gorm:"type:jsonb"`
|
||||
// Parser engine config overrides (MinerU endpoint, API key, etc.). Used when parsing documents; overrides env.
|
||||
ParserEngineConfig *ParserEngineConfig `yaml:"parser_engine_config" json:"parser_engine_config" gorm:"type:jsonb"`
|
||||
// Credentials config: third-party provider credentials (e.g. WeKnoraCloud AppID/AppSecret)
|
||||
Credentials *CredentialsConfig `yaml:"credentials" json:"credentials" gorm:"type:jsonb"`
|
||||
// Storage engine config: parameters for Local, MinIO, COS. Used for document/file storage and docreader.
|
||||
StorageEngineConfig *StorageEngineConfig `yaml:"storage_engine_config" json:"storage_engine_config" gorm:"type:jsonb"`
|
||||
// Chat history config: knowledge base configuration for indexing and searching chat messages via vector search
|
||||
@@ -230,13 +232,72 @@ func (c *ConversationConfig) Scan(value interface{}) error {
|
||||
return json.Unmarshal(b, c)
|
||||
}
|
||||
|
||||
// CredentialsConfig holds third-party provider credentials at the tenant level.
|
||||
// Stored as a single JSONB column; each provider is a nested object so new
|
||||
// providers can be added without schema changes.
|
||||
type CredentialsConfig struct {
|
||||
WeKnoraCloud *WeKnoraCloudCredentials `json:"weknoracloud,omitempty"`
|
||||
}
|
||||
|
||||
// WeKnoraCloudCredentials stores WeKnoraCloud AppID and AppSecret.
|
||||
// AppSecret is AES-256 encrypted before persisting to database.
|
||||
type WeKnoraCloudCredentials struct {
|
||||
AppID string `json:"app_id"`
|
||||
AppSecret string `json:"app_secret"`
|
||||
}
|
||||
|
||||
// GetWeKnoraCloud returns the WeKnoraCloud credentials, or nil if not configured.
|
||||
func (c *CredentialsConfig) GetWeKnoraCloud() *WeKnoraCloudCredentials {
|
||||
if c == nil || c.WeKnoraCloud == nil {
|
||||
return nil
|
||||
}
|
||||
if c.WeKnoraCloud.AppID == "" || c.WeKnoraCloud.AppSecret == "" {
|
||||
return nil
|
||||
}
|
||||
return c.WeKnoraCloud
|
||||
}
|
||||
|
||||
// Value implements the driver.Valuer interface for CredentialsConfig
|
||||
func (c *CredentialsConfig) Value() (driver.Value, error) {
|
||||
if c == nil {
|
||||
return nil, nil
|
||||
}
|
||||
cp := *c
|
||||
if cp.WeKnoraCloud != nil && cp.WeKnoraCloud.AppSecret != "" {
|
||||
if key := utils.GetAESKey(); key != nil {
|
||||
if encrypted, err := utils.EncryptAESGCM(cp.WeKnoraCloud.AppSecret, key); err == nil {
|
||||
cp.WeKnoraCloud = &WeKnoraCloudCredentials{AppID: cp.WeKnoraCloud.AppID, AppSecret: encrypted}
|
||||
}
|
||||
}
|
||||
}
|
||||
return json.Marshal(cp)
|
||||
}
|
||||
|
||||
// Scan implements the sql.Scanner interface for CredentialsConfig
|
||||
func (c *CredentialsConfig) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
b, ok := value.([]byte)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if err := json.Unmarshal(b, c); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.WeKnoraCloud != nil && c.WeKnoraCloud.AppSecret != "" {
|
||||
if key := utils.GetAESKey(); key != nil {
|
||||
if decrypted, err := utils.DecryptAESGCM(c.WeKnoraCloud.AppSecret, key); err == nil {
|
||||
c.WeKnoraCloud.AppSecret = decrypted
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParserEngineConfig holds tenant-level overrides for document parser engines (e.g. MinerU endpoint, API key).
|
||||
// These values take precedence over environment variables when parsing documents.
|
||||
type ParserEngineConfig struct {
|
||||
// docreader 凭证
|
||||
DocreaderAppID string `json:"docreader_app_id,omitempty"`
|
||||
DocreaderAPIKey string `json:"docreader_api_key,omitempty"`
|
||||
|
||||
MinerUEndpoint string `json:"mineru_endpoint"` // MinerU 自建服务端点
|
||||
MinerUAPIKey string `json:"mineru_api_key"` // MinerU 云 API Key
|
||||
|
||||
@@ -262,9 +323,6 @@ func (c *ParserEngineConfig) ToOverridesMap() map[string]string {
|
||||
return nil
|
||||
}
|
||||
m := make(map[string]string)
|
||||
if c.DocreaderAppID != "" {
|
||||
m["docreader_app_id"] = c.DocreaderAppID
|
||||
}
|
||||
if c.MinerUEndpoint != "" {
|
||||
m["mineru_endpoint"] = c.MinerUEndpoint
|
||||
}
|
||||
|
||||
@@ -6,8 +6,3 @@ type WeKnoraCloudStatusResult struct {
|
||||
NeedsReinit bool `json:"needs_reinit"` // 是否需要重新初始化(凭证损坏)
|
||||
Reason string `json:"reason,omitempty"` // 需要重新初始化的原因
|
||||
}
|
||||
|
||||
type DocreaderCredentials struct {
|
||||
AppID string
|
||||
APIKey string
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ CREATE TABLE IF NOT EXISTS tenants (
|
||||
web_search_config TEXT DEFAULT NULL,
|
||||
parser_engine_config TEXT DEFAULT NULL,
|
||||
storage_engine_config TEXT DEFAULT NULL,
|
||||
credentials TEXT DEFAULT NULL,
|
||||
chat_history_config TEXT,
|
||||
retrieval_config TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
1
migrations/versioned/000035_add_credentials.down.sql
Normal file
1
migrations/versioned/000035_add_credentials.down.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE tenants DROP COLUMN IF EXISTS credentials;
|
||||
5
migrations/versioned/000035_add_credentials.up.sql
Normal file
5
migrations/versioned/000035_add_credentials.up.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
-- Description: Add credentials column to tenants for third-party provider credentials (e.g. WeKnoraCloud AppID/AppSecret).
|
||||
DO $$ BEGIN RAISE NOTICE '[Migration 000035] Adding credentials column to tenants'; END $$;
|
||||
|
||||
ALTER TABLE tenants ADD COLUMN IF NOT EXISTS credentials JSONB DEFAULT NULL;
|
||||
COMMENT ON COLUMN tenants.credentials IS 'Third-party provider credentials (e.g. WeKnoraCloud AppID/AppSecret); encrypted at application level';
|
||||
Reference in New Issue
Block a user