feat[TS-7529]: support TOTP authentication (#33852)

This commit is contained in:
Bomin Zhang 2025-12-10 15:25:21 +08:00 committed by GitHub
parent ea2e280e14
commit d4e29c97fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 796 additions and 130 deletions

View file

@ -138,7 +138,8 @@ Below are the business error codes for each module.
| 0x8000022E | No available execution node | No available query execution node | Check the current query policy configuration, ensure available Qnode if needed |
| 0x8000022F | Table is not a supertable | Table name in the statement is not a supertable | Check if the table name used in the statement is a supertable |
| 0x80000230 | Stmt cache error | STMT/STMT2 internal cache error | Preserve the scene and logs, report issue on GitHub |
| 0x80000231 | Tsc internal error | TSC internal error | Preserve the scene and logs, report issue on GitHub |
| 0x80000238 | Invalid TOTP code | Invalid TOTP code | Check and enter the correct TOTP code |
| 0x800002FF | Tsc internal error | TSC internal error | Preserve the scene and logs, report issue on GitHub |
#### mnode
@ -179,6 +180,7 @@ Below are the business error codes for each module.
| 0x80000355 | Too many users | (Enterprise only) Exceeding user limit | Adjust configuration |
| 0x80000357 | Authentication failure | Incorrect password | Confirm if the operation is correct |
| 0x80000358 | User not available | User does not exist | Confirm if the operation is correct |
| 0x8000035B | Wrong TOTP code | TOTP code not provided or wrong TOTP code | Check and enter the correct TOTP code |
| 0x80000360 | STable already exists | Internal error | Report issue |
| 0x80000361 | STable not exist | Internal error | Report issue |
| 0x80000364 | Too many tags | Too many tags | Cannot be modified, code-level restriction |
@ -533,19 +535,19 @@ Below are the business error codes for each module.
| 0x8000268A | Cols function's first param must be a select function that output a single row | The first parameter of the cols function should be a selection function | Check and correct the SQL statement |
| 0x8000268B | Invalid using alias for cols function | Illegal cols function alias | Check and correct the SQL statement |
| 0x8000268C | Join primary key col must be timestamp type | Join primary key data type error | Check and correct the SQL statement |
| 0x8000268D | Invalid virtual table's ref column | Create/Update Virtual table using incorrect data source column | Check and correct the SQL statement |
| 0x8000268E | Invalid table type | Incorrect Table type | Check and correct the SQL statement |
| 0x8000268F | Invalid ref column type | Virtual table's column type and data source column's type are different | Check and correct the SQL statement |
| 0x80002690 | Create child table using virtual super table | Create non-virtual child table using virtual super table | Check and correct the SQL statement |
| 0x80002696 | Invalid sliding offset | Invalid sliding offset | Check and correct the SQL statement |
| 0x80002697 | Invalid interval offset | Invalid interval offset | Check and correct the SQL statement |
| 0x80002698 | Invalid extend value | Invalid extend value | Check and correct the SQL statement |
| 0x80002699 | Algorithm ID too long, max length is 63 character | Invalid algorithm id value | Check and correct the SQL statement |
| 0x8000269A | Algorithm name too long, max length is 63 character | Invalid algorithm name value | Check and correct the SQL statement |
| 0x8000269B | Algorithm description too long, max length is 127 character | Invalid algorithm description value | Check and correct the SQL statement |
| 0x8000269C | Algorithm type too long, max length is 63 character | Invalid algorithm type value | Check and correct the SQL statement |
| 0x8000269D | Algorithm OpenSSL name too long, max length is 63 character | Invalid algorithm OpenSSL name value | Check and correct the SQL statement |
| 0x8000269E | Option duplicated | Option is only allowed to appear once but appeared twice or more | Check and correct the SQL statement |
| 0x8000268D | Invalid virtual table's ref column | Create/Update Virtual table using incorrect data source column | Check and correct the SQL statement |
| 0x8000268E | Invalid table type | Incorrect Table type | Check and correct the SQL statement |
| 0x8000268F | Invalid ref column type | Virtual table's column type and data source column's type are different | Check and correct the SQL statement |
| 0x80002690 | Create child table using virtual super table | Create non-virtual child table using virtual super table | Check and correct the SQL statement |
| 0x80002696 | Invalid sliding offset | Invalid sliding offset | Check and correct the SQL statement |
| 0x80002697 | Invalid interval offset | Invalid interval offset | Check and correct the SQL statement |
| 0x80002698 | Invalid extend value | Invalid extend value | Check and correct the SQL statement |
| 0x80002699 | Algorithm ID too long, max length is 63 character | Invalid algorithm id value | Check and correct the SQL statement |
| 0x8000269A | Algorithm name too long, max length is 63 character | Invalid algorithm name value | Check and correct the SQL statement |
| 0x8000269B | Algorithm description too long, max length is 127 character | Invalid algorithm description value | Check and correct the SQL statement |
| 0x8000269C | Algorithm type too long, max length is 63 character | Invalid algorithm type value | Check and correct the SQL statement |
| 0x8000269D | Algorithm OpenSSL name too long, max length is 63 character | Invalid algorithm OpenSSL name value | Check and correct the SQL statement |
| 0x8000269E | Option duplicated | Option is only allowed to appear once but appeared twice or more | Check and correct the SQL statement |
| 0x8000269F | Invalid option value | Invalid option value | Check and correct the SQL statement |
| 0x800026A0 | Option value too long | Option value too long | Check and correct the SQL statement |
| 0x800026A1 | Option value too short | Option value too short | Check and correct the SQL statement |
@ -555,10 +557,10 @@ Below are the business error codes for each module.
| 0x80002700 | Planner internal error | Internal error in planner | Preserve the scene and logs, report issue on GitHub |
| 0x80002701 | Expect ts equal | JOIN condition validation failed | Preserve the scene and logs, report issue on GitHub |
| 0x80002702 | Cross join not support | CROSS JOIN not supported | Check and correct the SQL statement |
| 0x80002704 | Planner slot key not found | Planner cannot find slotId during making physic plan | Preserve the scene and logs, report issue on GitHub |
| 0x80002705 | Planner invalid table type | Planner get invalid table type | Preserve the scene and logs, report issue on GitHub |
| 0x80002706 | Planner invalid query control plan type | Planner get invalid query control plan type during making physic plan | Preserve the scene and logs, report issue on GitHub |
| 0x80002707 | Planner invalid window type | Planner get invalid window type during making physic plan | Preserve the scene and logs, report issue on GitHub |
| 0x80002704 | Planner slot key not found | Planner cannot find slotId during making physic plan | Preserve the scene and logs, report issue on GitHub |
| 0x80002705 | Planner invalid table type | Planner get invalid table type | Preserve the scene and logs, report issue on GitHub |
| 0x80002706 | Planner invalid query control plan type | Planner get invalid query control plan type during making physic plan | Preserve the scene and logs, report issue on GitHub |
| 0x80002707 | Planner invalid window type | Planner get invalid window type during making physic plan | Preserve the scene and logs, report issue on GitHub |
#### function

View file

@ -136,7 +136,8 @@ TSDB 错误码包括 taosc 客户端和服务端,所有语言的连接器无
| 0x8000022E | No available execution node | 没有可用的查询执行节点 | 检查当前 query policy 配置,如果需要有 Qnode 参与确保系统中存在可用的 Qnode 节点 |
| 0x8000022F | Table is not a super table | 当前语句中的表名不是超级表 | 检查当前语句中所用表名是否是超级表 |
| 0x80000230 | Stmt cache error | STMT/STMT2 内部缓存出错 | 保留现场和日志github 上报 issue |
| 0x80000231 | Tsc internal error | TSC 内部错误 | 保留现场和日志github 上报 issue |
| 0x80000238 | Invalid TOTP code | 输入的 TOTP 验证码格式错误 | 检查并重新输入正确的 TOTP 验证码 |
| 0x800002FF | Tsc internal error | TSC 内部错误 | 保留现场和日志github 上报 issue |
#### mnode
@ -164,7 +165,7 @@ TSDB 错误码包括 taosc 客户端和服务端,所有语言的连接器无
| 0x80000332 | Vgroup does not exist | 内部错误 | 上报 issue |
| 0x80000333 | Cannot drop mnode which is leader | 操作节点为 leader | 确认操作是否正确 |
| 0x80000334 | Out of dnodes | dnode 节点数量不够 | 增加 dnode 节点 |
| 0x80000335 | Cluster cfg inconsistent | 配置不一致 | 检查 dnode 节点与 mnode 节点配置是否一致。检查方式1.节点启动时,在日志中输出 2.使用 show variables |
| 0x80000335 | Cluster cfg inconsistent | 配置不一致 | 检查 dnode 节点与 mnode 节点配置是否一致。检查方式1.节点启动时,在日志中输出 2.使用 show variables |
| 0x8000033B | Cluster id not match | 节点配置数据不一致 | 检查各节点 data/dnode/dnodes.json 文件中的 clusterid |
| 0x80000340 | Account already exists | (仅企业版)内部错误 | 上报 issue |
| 0x80000342 | Invalid account options | (仅企业版)该操作不支持 | 确认操作是否正确 |
@ -177,6 +178,7 @@ TSDB 错误码包括 taosc 客户端和服务端,所有语言的连接器无
| 0x80000355 | Too many users | (仅企业版)用户数量超限 | 调整配置 |
| 0x80000357 | Authentication failure | 密码不正确 | 确认操作是否正确 |
| 0x80000358 | User not available | 用户不存在 | 确认操作是否正确 |
| 0x8000035B | Wrong TOTP code | 未提供或提供了错误的 TOTP 验证码 | 检查并输入正确的 TOTP 验证码 |
| 0x80000360 | STable already exists | 内部错误 | 上报 issue |
| 0x80000361 | STable not exist | 内部错误 | 上报 issue |
| 0x80000364 | Too many tags | tag 数量太多 | 不能修改,代码级别限制 |
@ -481,7 +483,7 @@ TSDB 错误码包括 taosc 客户端和服务端,所有语言的连接器无
| 0x80002635 | Incorrect TIMESTAMP value | 主键时间戳列值非法 | 检查并修正 SQL 语句 |
| 0x80002637 | soffset/offset can not be less than 0 | soffset/offset 值非法 | 检查并修正 SQL 语句 |
| 0x80002638 | slimit/soffset only available for PARTITION/GROUP BY query | slimit/soffset 只支持 PARTITION BY/GROUP BY 语句 | 检查并修正 SQL 语句 |
| 0x80002639 | Invalid topic query | 不支持的 TOPIC 查询语法 | 检查并修正 SQL 语句 |
| 0x80002639 | Invalid topic query | 不支持的 TOPIC 查询语法 | 检查并修正 SQL 语句 |
| 0x8000263A | Cannot drop super table in batch | 不支持批量删除超级表 | 检查并修正 SQL 语句 |
| 0x8000263B | Start(end) time of query range required or time range too large | 窗口个数超出限制 | 检查并修正 SQL 语句 |
| 0x8000263C | Duplicated column names | 列名称重复 | 检查并修正 SQL 语句 |
@ -538,12 +540,12 @@ TSDB 错误码包括 taosc 客户端和服务端,所有语言的连接器无
| 0x80002696 | Invalid sliding offset | sliding 窗口偏移量非法 | 检查并修正 SQL 语句 |
| 0x80002697 | Invalid interval offset | interval 窗口偏移量非法 | 检查并修正 SQL 语句 |
| 0x80002698 | Invalid extend value | extend 参数非法 | 检查并修正 SQL 语句 |
| 0x80002699 | Algorithm ID too long, max length is 63 character | Algorithm ID 参数非法 | 检查并修正 SQL 语句 |
| 0x8000269A | Algorithm name too long, max length is 63 character | Algorithm name 参数非法 | 检查并修正 SQL 语句 |
| 0x8000269B | Algorithm description too long, max length is 127 character | Algorithm description 参数非法 | 检查并修正 SQL 语句 |
| 0x8000269C | Algorithm type too long, max length is 63 character | Algorithm type 参数非法 | 检查并修正 SQL 语句 |
| 0x8000269D | Algorithm OpenSSL name too long, max length is 63 character | Algorithm OpenSSL name 参数非法 | 检查并修正 SQL 语句 |
| 0x8000269E | Option duplicated | 只允许出现一次的选项出现了多次 | 检查并修正 SQL 语句 |
| 0x80002699 | Algorithm ID too long, max length is 63 character | Algorithm ID 参数非法 | 检查并修正 SQL 语句 |
| 0x8000269A | Algorithm name too long, max length is 63 character | Algorithm name 参数非法 | 检查并修正 SQL 语句 |
| 0x8000269B | Algorithm description too long, max length is 127 character | Algorithm description 参数非法 | 检查并修正 SQL 语句 |
| 0x8000269C | Algorithm type too long, max length is 63 character | Algorithm type 参数非法 | 检查并修正 SQL 语句 |
| 0x8000269D | Algorithm OpenSSL name too long, max length is 63 character | Algorithm OpenSSL name 参数非法 | 检查并修正 SQL 语句 |
| 0x8000269E | Option duplicated | 只允许出现一次的选项出现了多次 | 检查并修正 SQL 语句 |
| 0x8000269F | Invalid option value | 选项的值非法 | 检查并修正 SQL 语句 |
| 0x800026A0 | Option value too long | 选项的值太长 | 检查并修正 SQL 语句 |
| 0x800026A1 | Option value too short | 选项的值太短 | 检查并修正 SQL 语句 |
@ -556,7 +558,7 @@ TSDB 错误码包括 taosc 客户端和服务端,所有语言的连接器无
| 0x80002704 | Planner slot key not found | 生成物理计划时查找不到 slotId | 保留现场和日志github 上报 issue |
| 0x80002705 | Planner invalid table type | 计划器生成计划时得到了错误的表类型 | 保留现场和日志github 上报 issue |
| 0x80002706 | Planner invalid query control plan type | 计划器生成 dynamic query control 计划时得到的类型不正确 | 保留现场和日志github 上报 issue |
| 0x80002707 | Planner invalid window type | 计划器生成物理计划时得到了错误的窗口类型 | 保留现场和日志github 上报 issue |
| 0x80002707 | Planner invalid window type | 计划器生成物理计划时得到了错误的窗口类型 | 保留现场和日志github 上报 issue |
#### function

View file

@ -204,6 +204,9 @@ DLL_EXPORT TAOS *taos_connect_auth(const char *ip, const char *user, const char
* @return TAOS* connection handle on success; NULL if unsupported or on error
*/
DLL_EXPORT TAOS *taos_connect_with_dsn(const char *dsn);
DLL_EXPORT TAOS *taos_connect_totp(const char *ip, const char *user, const char *pass, const char* totp, const char *db, uint16_t port);
DLL_EXPORT int taos_connect_test(const char *ip, const char *user, const char *pass, const char* totp, const char *db, uint16_t port);
DLL_EXPORT TAOS *taos_connect_token(const char *ip, const char *token, const char *db, uint16_t port);
DLL_EXPORT void taos_close(TAOS *taos);
DLL_EXPORT const char *taos_data_type(int type);

View file

@ -131,6 +131,7 @@ enum {
CONN_TYPE__QUERY = 1,
CONN_TYPE__TMQ,
CONN_TYPE__UDFD,
CONN_TYPE__AUTH_TEST, // only for test authentication
CONN_TYPE__MAX,
};
@ -1277,6 +1278,7 @@ typedef struct {
char passwd[TSDB_PASSWORD_LEN];
int64_t startTime;
char sVer[TSDB_VERSION_LEN];
int32_t totpCode;
} SConnectReq;
int32_t tSerializeSConnectReq(void* buf, int32_t bufLen, SConnectReq* pReq);
@ -1290,7 +1292,6 @@ typedef struct {
int8_t superUser;
int8_t sysInfo;
int8_t connType;
int8_t mustChangePass;
SEpSet epSet;
int32_t svrTimestamp;
int32_t passVer;

View file

@ -126,7 +126,8 @@ typedef enum EFunctionType {
FUNCTION_TYPE_FIND_IN_SET,
FUNCTION_TYPE_LIKE_IN_SET,
FUNCTION_TYPE_REGEXP_IN_SET,
FUNCTION_TYPE_GENERATE_TOTP_SECRET,
FUNCTION_TYPE_GENERATE_TOTP_CODE,
// conversion function
FUNCTION_TYPE_CAST = 2000,

View file

@ -103,6 +103,8 @@ int32_t crc32Function(SScalarParam* pInput, int32_t inputNum, SScalarParam* pOut
int32_t findInSetFunction(SScalarParam* pInput, int32_t inputNum, SScalarParam* pOutput);
int32_t likeInSetFunction(SScalarParam* pInput, int32_t inputNum, SScalarParam* pOutput);
int32_t regexpInSetFunction(SScalarParam* pInput, int32_t inputNum, SScalarParam* pOutput);
int32_t generateTotpSecretFunction(SScalarParam* pInput, int32_t inputNum, SScalarParam* pOutput);
int32_t generateTotpCodeFunction(SScalarParam* pInput, int32_t inputNum, SScalarParam* pOutput);
/* Conversion functions */
int32_t castFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput);

View file

@ -222,6 +222,7 @@ int32_t taosGetErrSize();
#define TSDB_CODE_TSC_FAIL_GENERATE_JSON TAOS_DEF_ERROR_CODE(0, 0x0235)
#define TSDB_CODE_TSC_STMT_BIND_NUMBER_ERROR TAOS_DEF_ERROR_CODE(0, 0x0236)
#define TSDB_CODE_NOT_SUPPORTTED_IN_WINDOWS TAOS_DEF_ERROR_CODE(0, 0x0237)
#define TSDB_CODE_TSC_INVALID_TOTP_CODE TAOS_DEF_ERROR_CODE(0, 0x0238)
#define TSDB_CODE_TSC_INTERNAL_ERROR TAOS_DEF_ERROR_CODE(0, 0x02FF)
// mnode-common
@ -304,7 +305,7 @@ int32_t taosGetErrSize();
#define TSDB_CODE_MND_USER_NOT_AVAILABLE TAOS_DEF_ERROR_CODE(0, 0x0358)
#define TSDB_CODE_MND_PRIVILEDGE_EXIST TAOS_DEF_ERROR_CODE(0, 0x0359)
#define TSDB_CODE_MND_USER_PASSWORD_REUSE TAOS_DEF_ERROR_CODE(0, 0x035A)
// #define TSDB_CODE_MND_USER_TIME_RANGE_CONFLICT TAOS_DEF_ERROR_CODE(0, 0x035B)
#define TSDB_CODE_MND_WRONG_TOTP_CODE TAOS_DEF_ERROR_CODE(0, 0x035B)
#define TSDB_CODE_MND_TOO_MANY_USER_IP_RANGE TAOS_DEF_ERROR_CODE(0, 0x035C)
#define TSDB_CODE_MND_TOO_MANY_USER_TIME_RANGE TAOS_DEF_ERROR_CODE(0, 0x035D)

53
include/util/totp.h Normal file
View file

@ -0,0 +1,53 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* This program is free software: you can use, redistribute, and/or modify
* it under the terms of the GNU Affero General Public License, version 3
* or later ("AGPL"), as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _TD_UTIL_TOTP_H_
#define _TD_UTIL_TOTP_H_
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
// format the TOTP code with leading zeros to match the specified digit length.
// return the number of characters written, excluding the null terminator.
int taosFormatTotp(int32_t totp, int digits, char *buffer, size_t size);
// generate TOTP code for given secret at current time.
// secret is a byte array, not a base32 string.
// return the generated TOTP code, or -1 on error.
int32_t taosGenerateTotpCode(const uint8_t *secret, size_t secretLen, int digits);
// verify TOTP code for given secret at current time with allowed window.
// secret is a byte array, not a base32 string.
// return 1 if the code is correct, 0 otherwise.
int taosVerifyTotpCode(const uint8_t *secret, size_t secretLen, int32_t userCode, int digits, int window);
// generate TOTP secret from seed
// if seedLen is 0, the seed is treated as a null-terminated string and its length is determined using strlen.
// secret is a byte array, not a base32 string.
// return the length of the generated secret, or -1 on error.
int taosGenerateTotpSecret(const char *seed, size_t seedLen, uint8_t *secret, size_t secretLen);
#ifdef __cplusplus
}
#endif
#endif /*_TD_UTIL_TOTP_H_*/

View file

@ -381,6 +381,9 @@ bool taosIsBigChar(char c);
bool taosIsSmallChar(char c);
bool taosIsNumberChar(char c);
bool taosIsSpecialChar(char c);
// check if the string is a complex string, a complex string contains
// at least 3 types of characters: upper, lower, digit, special
bool taosIsComplexString(const char* str);
#ifdef __cplusplus
}

View file

@ -186,8 +186,8 @@ typedef struct STscObj {
SAppInstInfo* pAppInfo;
SHashObj* pRequests;
SPassInfo passInfo;
SWhiteListInfo whiteListInfo;
SWhiteListInfo dateTimeWhiteListInfo;
SWhiteListInfo whiteListInfo; // ip white list info
SWhiteListInfo dateTimeWhiteListInfo; // date time white list info
STscNotifyInfo userDroppedInfo;
SOptionInfo optionInfo;
} STscObj;
@ -394,7 +394,7 @@ typedef struct AsyncArg {
bool persistConnForSpecificMsg(void* parenct, tmsg_t msgType);
void processMsgFromServer(void* parent, SRpcMsg* pMsg, SEpSet* pEpSet);
int32_t taos_connect_internal(const char* ip, const char* user, const char* pass, const char* auth, const char* db,
int32_t taos_connect_internal(const char* ip, const char* user, const char* pass, const char* auth, const char* totp, const char* db,
uint16_t port, int connType, STscObj** pObj);
int32_t parseSql(SRequestObj* pRequest, bool topicQuery, SQuery** pQuery, SStmtCallback* pStmtCb);

View file

@ -33,7 +33,7 @@
#include "tversion.h"
static int32_t initEpSetFromCfg(const char* firstEp, const char* secondEp, SCorEpSet* pEpSet);
static int32_t buildConnectMsg(SRequestObj* pRequest, SMsgSendInfo** pMsgSendInfo);
static int32_t buildConnectMsg(SRequestObj* pRequest, SMsgSendInfo** pMsgSendInfo, int32_t totpCode);
void setQueryRequest(int64_t rId) {
SRequestObj* pReq = acquireRequest(rId);
@ -143,10 +143,10 @@ void cleanupAppInfo() {
tscInfo("cluster instance map cleaned");
}
static int32_t taosConnectImpl(const char* user, const char* auth, const char* db, __taos_async_fn_t fp, void* param,
static int32_t taosConnectImpl(const char* user, const char* auth, int32_t totpCode, const char* db, __taos_async_fn_t fp, void* param,
SAppInstInfo* pAppInfo, int connType, STscObj** pTscObj);
int32_t taos_connect_internal(const char* ip, const char* user, const char* pass, const char* auth, const char* db,
int32_t taos_connect_internal(const char* ip, const char* user, const char* pass, const char* auth, const char* totp, const char* db,
uint16_t port, int connType, STscObj** pObj) {
TSC_ERR_RET(taos_init());
if (!validateUserName(user)) {
@ -175,6 +175,15 @@ int32_t taos_connect_internal(const char* ip, const char* user, const char* pass
tstrncpy(secretEncrypt, auth, tListLen(secretEncrypt));
}
int32_t totpCode = -1;
if (totp != NULL) {
char *endptr = NULL;
totpCode = taosStr2Int32(totp, &endptr, 10);
if (endptr == totp || *endptr != '\0' || totpCode < 0 || totpCode > 999999) {
TSC_ERR_RET(TSDB_CODE_TSC_INVALID_TOTP_CODE);
}
}
SCorEpSet epSet = {0};
if (ip) {
TSC_ERR_RET(initEpSetFromCfg(ip, NULL, &epSet));
@ -267,7 +276,7 @@ _return:
tscError("failed to unlock app info, code:%s", tstrerror(TAOS_SYSTEM_ERROR(code)));
return code;
}
return taosConnectImpl(user, &secretEncrypt[0], localDb, NULL, NULL, *pInst, connType, pObj);
return taosConnectImpl(user, &secretEncrypt[0], totpCode, localDb, NULL, NULL, *pInst, connType, pObj);
}
}
@ -1688,7 +1697,7 @@ int32_t initEpSetFromCfg(const char* firstEp, const char* secondEp, SCorEpSet* p
return 0;
}
int32_t taosConnectImpl(const char* user, const char* auth, const char* db, __taos_async_fn_t fp, void* param,
int32_t taosConnectImpl(const char* user, const char* auth, int32_t totpCode, const char* db, __taos_async_fn_t fp, void* param,
SAppInstInfo* pAppInfo, int connType, STscObj** pTscObj) {
*pTscObj = NULL;
int32_t code = createTscObj(user, auth, db, connType, pAppInfo, pTscObj);
@ -1711,7 +1720,7 @@ int32_t taosConnectImpl(const char* user, const char* auth, const char* db, __ta
}
SMsgSendInfo* body = NULL;
code = buildConnectMsg(pRequest, &body);
code = buildConnectMsg(pRequest, &body, totpCode);
if (TSDB_CODE_SUCCESS != code) {
destroyTscObj(*pTscObj);
return code;
@ -1739,15 +1748,22 @@ int32_t taosConnectImpl(const char* user, const char* auth, const char* db, __ta
taos_close_internal(*pTscObj);
*pTscObj = NULL;
return terrno;
} else {
tscInfo("conn:0x%" PRIx64 ", connection is opening, connId:%u, dnodeConn:%p, QID:0x%" PRIx64, (*pTscObj)->id,
(*pTscObj)->connId, (*pTscObj)->pAppInfo->pTransporter, pRequest->requestId);
destroyRequest(pRequest);
}
if (connType == CONN_TYPE__AUTH_TEST) {
terrno = TSDB_CODE_SUCCESS;
destroyRequest(pRequest);
taos_close_internal(*pTscObj);
*pTscObj = NULL;
return TSDB_CODE_SUCCESS;
}
tscInfo("conn:0x%" PRIx64 ", connection is opening, connId:%u, dnodeConn:%p, QID:0x%" PRIx64, (*pTscObj)->id,
(*pTscObj)->connId, (*pTscObj)->pAppInfo->pTransporter, pRequest->requestId);
destroyRequest(pRequest);
return code;
}
static int32_t buildConnectMsg(SRequestObj* pRequest, SMsgSendInfo** pMsgSendInfo) {
static int32_t buildConnectMsg(SRequestObj* pRequest, SMsgSendInfo** pMsgSendInfo, int32_t totpCode) {
*pMsgSendInfo = taosMemoryCalloc(1, sizeof(SMsgSendInfo));
if (*pMsgSendInfo == NULL) {
return terrno;
@ -1781,6 +1797,7 @@ static int32_t buildConnectMsg(SRequestObj* pRequest, SMsgSendInfo** pMsgSendInf
connectReq.connType = pObj->connType;
connectReq.pid = appInfo.pid;
connectReq.startTime = appInfo.startTime;
connectReq.totpCode = totpCode;
tstrncpy(connectReq.app, appInfo.appName, sizeof(connectReq.app));
tstrncpy(connectReq.user, pObj->user, sizeof(connectReq.user));
@ -1990,6 +2007,56 @@ _exit:
}
}
TAOS *taos_connect_totp(const char *ip, const char *user, const char *pass, const char* totp, const char *db, uint16_t port) {
tscInfo("try to connect to %s:%u by totp, user:%s db:%s", ip, port, user, db);
if (user == NULL) {
user = TSDB_DEFAULT_USER;
}
if (pass == NULL) {
pass = TSDB_DEFAULT_PASS;
}
STscObj *pObj = NULL;
int32_t code = taos_connect_internal(ip, user, pass, NULL, totp, db, port, CONN_TYPE__QUERY, &pObj);
if (TSDB_CODE_SUCCESS == code) {
int64_t *rid = taosMemoryCalloc(1, sizeof(int64_t));
if (NULL == rid) {
tscError("out of memory when taos connect to %s:%u, user:%s db:%s", ip, port, user, db);
return NULL;
}
*rid = pObj->id;
return (TAOS *)rid;
} else {
terrno = code;
}
return NULL;
}
int taos_connect_test(const char *ip, const char *user, const char *pass, const char* totp, const char *db, uint16_t port) {
tscInfo("try to connect to %s:%u by totp, user:%s db:%s", ip, port, user, db);
if (user == NULL) {
user = TSDB_DEFAULT_USER;
}
if (pass == NULL) {
pass = TSDB_DEFAULT_PASS;
}
STscObj *pObj = NULL;
return taos_connect_internal(ip, user, pass, NULL, totp, db, port, CONN_TYPE__AUTH_TEST, &pObj);
}
TAOS *taos_connect_token(const char *ip, const char *token, const char *db, uint16_t port) {
return NULL;
}
TAOS* taos_connect_auth(const char* ip, const char* user, const char* auth, const char* db, uint16_t port) {
tscInfo("try to connect to %s:%u by auth, user:%s db:%s", ip, port, user, db);
if (user == NULL) {
@ -2002,7 +2069,7 @@ TAOS* taos_connect_auth(const char* ip, const char* user, const char* auth, cons
}
STscObj* pObj = NULL;
int32_t code = taos_connect_internal(ip, user, NULL, auth, db, port, CONN_TYPE__QUERY, &pObj);
int32_t code = taos_connect_internal(ip, user, NULL, auth, NULL, db, port, CONN_TYPE__QUERY, &pObj);
if (TSDB_CODE_SUCCESS == code) {
int64_t* rid = taosMemoryCalloc(1, sizeof(int64_t));
if (NULL == rid) {

View file

@ -319,7 +319,7 @@ TAOS *taos_connect(const char *ip, const char *user, const char *pass, const cha
}
STscObj *pObj = NULL;
int32_t code = taos_connect_internal(ip, user, pass, NULL, db, port, CONN_TYPE__QUERY, &pObj);
int32_t code = taos_connect_internal(ip, user, pass, NULL, NULL, db, port, CONN_TYPE__QUERY, &pObj);
if (TSDB_CODE_SUCCESS == code) {
int64_t *rid = taosMemoryCalloc(1, sizeof(int64_t));
if (NULL == rid) {

View file

@ -74,6 +74,11 @@ int32_t processConnectRsp(void* param, SDataBuf* pMsg, int32_t code) {
goto End;
}
if (pTscObj->connType == CONN_TYPE__AUTH_TEST) {
// auth test connection, no need to process connect rsp
goto End;
}
SConnectRsp connectRsp = {0};
if (tDeserializeSConnectRsp(pMsg->pData, pMsg->len, &connectRsp) != 0) {
code = TSDB_CODE_TSC_INVALID_VERSION;

View file

@ -1841,7 +1841,7 @@ tmq_t* tmq_consumer_new(tmq_conf_t* conf, char* errstr, int32_t errstrLen) {
}
// init connection
code = taos_connect_internal(conf->ip, user, pass, NULL, NULL, conf->port, CONN_TYPE__TMQ, &pTmq->pTscObj);
code = taos_connect_internal(conf->ip, user, pass, NULL, NULL, NULL, conf->port, CONN_TYPE__TMQ, &pTmq->pTscObj);
if (code) {
terrno = code;
tqErrorC("consumer:0x%" PRIx64 " setup failed since %s, groupId:%s", pTmq->consumerId, terrstr(), pTmq->groupId);

View file

@ -19,6 +19,7 @@
#include "osSemaphore.h"
#include "taoserror.h"
#include "thash.h"
#include "totp.h"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
@ -338,6 +339,62 @@ TEST(clientCase, connect_Test) {
taos_close(pConn);
}
TEST(clientCase, connect_totp_Test) {
taos_options(TSDB_OPTION_CONFIGDIR, "~/first/cfg");
TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0);
if (pConn == NULL) {
(void)printf("failed to connect to server, reason:%s\n", taos_errstr(NULL));
}
uint8_t secret[64] = {0};
size_t secretLen = taosGenerateTotpSecret("AAbb1122", 8, secret, sizeof(secret));
TAOS_RES* pRes = taos_query(pConn, "create user totp_u pass 'taosdata' totpseed 'AAbb1122'");
if (taos_errno(pRes) != 0) {
(void)printf("error in create user, reason:%s\n", taos_errstr(pRes));
}
taos_free_result(pRes);
taos_close(pConn);
int totpCode = taosGenerateTotpCode(secret, secretLen, 6);
pConn = taos_connect_totp("localhost", "totp_u", "taosdata", "123456", NULL, 0);
if (pConn != NULL) {
(void)printf("connect to server with wrong totp");
taos_close(pConn);
}
int code = taos_connect_test("localhost", "totp_u", "taosdata", "123456", NULL, 0);
if (code != TSDB_CODE_MND_WRONG_TOTP_CODE) {
(void)printf("test connect to server with wrong totp return wrong code:%d\n", code);
taos_close(pConn);
}
char totp[16] = {0};
(void)taosFormatTotp(totpCode, 6, totp, sizeof(totp));
pConn = taos_connect_totp("localhost", "totp_u", "taosdata", totp, NULL, 0);
if (pConn == NULL) {
(void)printf("failed to connect to server with totp, reason:%s\n", taos_errstr(NULL));
}
pRes = taos_query(pConn, "show users");
if (taos_errno(pRes) != 0) {
(void)printf("error in create user, reason:%s\n", taos_errstr(pRes));
}
taos_free_result(pRes);
taos_close(pConn);
totpCode = taosGenerateTotpCode(secret, secretLen, 6);
(void)taosFormatTotp(totpCode, 6, totp, sizeof(totp));
code = taos_connect_test("localhost", "totp_u", "taosdata", totp, NULL, 0);
if (code != 0) {
(void)printf("test connect to server with correct totp return wrong code:%d\n", code);
}
}
TEST(clientCase, connect_with_dsn_Test) {
TAOS* pConn = taos_connect_with_dsn(NULL);
ASSERT_EQ(pConn, nullptr);

View file

@ -42,6 +42,9 @@ extern void (*fp_taos_cleanup)(void);
extern int (*fp_taos_options)(TSDB_OPTION option, const void *arg, ...);
extern int (*fp_taos_options_connection)(TAOS *taos, TSDB_OPTION_CONNECTION option, const void *arg, ...);
extern TAOS *(*fp_taos_connect)(const char *ip, const char *user, const char *pass, const char *db, uint16_t port);
extern TAOS *(*fp_taos_connect_totp)(const char *ip, const char *user, const char *pass, const char* totp, const char *db, uint16_t port);
extern int (*fp_taos_connect_test)(const char *ip, const char *user, const char *pass, const char* totp, const char *db, uint16_t port);
extern TAOS *(*fp_taos_connect_token)(const char *ip, const char *token, const char *db, uint16_t port);
extern TAOS *(*fp_taos_connect_auth)(const char *ip, const char *user, const char *auth, const char *db, uint16_t port);
extern TAOS *(*fp_taos_connect_with_dsn)(const char *dsn);
extern void (*fp_taos_close)(TAOS *taos);

View file

@ -95,6 +95,9 @@ int32_t taosDriverInit(EDriverType driverType) {
LOAD_FUNC(fp_taos_options, "taos_options");
LOAD_FUNC(fp_taos_options_connection, "taos_options_connection");
LOAD_FUNC(fp_taos_connect, "taos_connect");
LOAD_FUNC(fp_taos_connect_totp, "taos_connect_totp");
LOAD_FUNC(fp_taos_connect_token, "taos_connect_token");
LOAD_FUNC(fp_taos_connect_test, "taos_connect_test");
LOAD_FUNC(fp_taos_connect_auth, "taos_connect_auth");
LOAD_FUNC(fp_taos_connect_with_dsn, "taos_connect_with_dsn");
LOAD_FUNC(fp_taos_close, "taos_close");

View file

@ -154,6 +154,39 @@ TAOS *taos_connect(const char *ip, const char *user, const char *pass, const cha
return (*fp_taos_connect)(ip, user, pass, db, port);
}
TAOS *taos_connect_totp(const char *ip, const char *user, const char *pass, const char* totp, const char *db, uint16_t port) {
if (taos_init() != 0) {
terrno = TSDB_CODE_DLL_NOT_LOAD;
return NULL;
}
CHECK_PTR(fp_taos_connect_totp);
return (*fp_taos_connect_totp)(ip, user, pass, totp, db, port);
}
int taos_connect_test(const char *ip, const char *user, const char *pass, const char* totp, const char *db, uint16_t port) {
if (taos_init() != 0) {
return TSDB_CODE_DLL_NOT_LOAD;
}
if (tsDriver == NULL) {
return TSDB_CODE_DLL_NOT_LOAD;
}
if (fp_taos_connect_test == NULL) {
return TSDB_CODE_DLL_FUNC_NOT_LOAD;
}
return (*fp_taos_connect_test)(ip, user, pass, totp, db, port);
}
TAOS *taos_connect_token(const char *ip, const char *token, const char *db, uint16_t port) {
if (taos_init() != 0) {
terrno = TSDB_CODE_DLL_NOT_LOAD;
return NULL;
}
CHECK_PTR(fp_taos_connect_token);
return (*fp_taos_connect_token)(ip, token, db, port);
}
TAOS *taos_connect_auth(const char *ip, const char *user, const char *auth, const char *db, uint16_t port) {
if (taos_init() != 0) {
terrno = TSDB_CODE_DLL_NOT_LOAD;

View file

@ -22,6 +22,9 @@ void (*fp_taos_cleanup)(void) = NULL;
int (*fp_taos_options)(TSDB_OPTION option, const void *arg, ...) = NULL;
int (*fp_taos_options_connection)(TAOS *taos, TSDB_OPTION_CONNECTION option, const void *arg, ...) = NULL;
TAOS *(*fp_taos_connect)(const char *ip, const char *user, const char *pass, const char *db, uint16_t port) = NULL;
TAOS *(*fp_taos_connect_totp)(const char *ip, const char *user, const char *pass, const char* totp, const char *db, uint16_t port) = NULL;
int (*fp_taos_connect_test)(const char *ip, const char *user, const char *pass, const char* totp, const char *db, uint16_t port) = NULL;
TAOS *(*fp_taos_connect_token)(const char *ip, const char *token, const char *db, uint16_t port) = NULL;
TAOS *(*fp_taos_connect_auth)(const char *ip, const char *user, const char *auth, const char *db, uint16_t port) = NULL;
TAOS *(*fp_taos_connect_with_dsn)(const char *dsn) = NULL;
void (*fp_taos_close)(TAOS *taos) = NULL;

View file

@ -9358,6 +9358,7 @@ int32_t tSerializeSConnectReq(void *buf, int32_t bufLen, SConnectReq *pReq) {
TAOS_CHECK_EXIT(tEncodeCStrWithLen(&encoder, pReq->passwd, TSDB_PASSWORD_LEN));
TAOS_CHECK_EXIT(tEncodeI64(&encoder, pReq->startTime));
TAOS_CHECK_EXIT(tEncodeCStr(&encoder, pReq->sVer));
TAOS_CHECK_EXIT(tEncodeI32(&encoder, pReq->totpCode));
tEndEncode(&encoder);
_exit:
@ -9390,6 +9391,12 @@ int32_t tDeserializeSConnectReq(void *buf, int32_t bufLen, SConnectReq *pReq) {
TAOS_CHECK_EXIT(TSDB_CODE_VERSION_NOT_COMPATIBLE);
}
TAOS_CHECK_EXIT(tDecodeCStrTo(&decoder, pReq->sVer));
// Check the client version from version 3.4.0.0
if (tDecodeIsEnd(&decoder)) {
tDecoderClear(&decoder);
TAOS_CHECK_EXIT(TSDB_CODE_VERSION_NOT_COMPATIBLE);
}
TAOS_CHECK_EXIT(tDecodeI32(&decoder, &pReq->totpCode));
tEndDecode(&decoder);
_exit:
@ -9422,7 +9429,6 @@ int32_t tSerializeSConnectRsp(void *buf, int32_t bufLen, SConnectRsp *pRsp) {
TAOS_CHECK_EXIT(tSerializeSMonitorParas(&encoder, &pRsp->monitorParas));
TAOS_CHECK_EXIT(tEncodeI8(&encoder, pRsp->enableAuditDelete));
TAOS_CHECK_EXIT(tEncodeI64(&encoder, pRsp->timeWhiteListVer));
TAOS_CHECK_EXIT(tEncodeI8(&encoder, pRsp->mustChangePass));
tEndEncode(&encoder);
_exit:
@ -9481,10 +9487,8 @@ int32_t tDeserializeSConnectRsp(void *buf, int32_t bufLen, SConnectRsp *pRsp) {
}
if (!tDecodeIsEnd(&decoder)) {
TAOS_CHECK_EXIT(tDecodeI64(&decoder, &pRsp->timeWhiteListVer));
TAOS_CHECK_EXIT(tDecodeI8(&decoder, &pRsp->mustChangePass));
} else {
pRsp->timeWhiteListVer = 0;
pRsp->mustChangePass = 0;
}
tEndDecode(&decoder);

View file

@ -291,6 +291,7 @@ static const SSysDbTableSchema userUsersSchema[] = {
{.name = "sysinfo", .bytes = 1, .type = TSDB_DATA_TYPE_TINYINT, .sysInfo = true},
{.name = "createdb", .bytes = 1, .type = TSDB_DATA_TYPE_TINYINT, .sysInfo = true},
{.name = "create_time", .bytes = 8, .type = TSDB_DATA_TYPE_TIMESTAMP, .sysInfo = true},
{.name = "totp", .bytes = 1, .type = TSDB_DATA_TYPE_TINYINT, .sysInfo = true},
{.name = "allowed_host", .bytes = TSDB_PRIVILEDGE_HOST_LEN + VARSTR_HEADER_SIZE, .type = TSDB_DATA_TYPE_VARCHAR, .sysInfo = true},
{.name = "allowed_datetime", .bytes = TSDB_PRIVILEDGE_HOST_LEN + VARSTR_HEADER_SIZE, .type = TSDB_DATA_TYPE_VARCHAR, .sysInfo = true},
};
@ -302,6 +303,7 @@ static const SSysDbTableSchema userUsersFullSchema[] = {
{.name = "sysinfo", .bytes = 1, .type = TSDB_DATA_TYPE_TINYINT, .sysInfo = true},
{.name = "createdb", .bytes = 1, .type = TSDB_DATA_TYPE_TINYINT, .sysInfo = true},
{.name = "create_time", .bytes = 8, .type = TSDB_DATA_TYPE_TIMESTAMP, .sysInfo = true},
{.name = "totp", .bytes = 1, .type = TSDB_DATA_TYPE_TINYINT, .sysInfo = true},
{.name = "change_pass", .bytes = 1, .type = TSDB_DATA_TYPE_TINYINT, .sysInfo = true},
{.name = "encrypted_pass", .bytes = TSDB_PASSWORD_LEN + VARSTR_HEADER_SIZE, .type = TSDB_DATA_TYPE_VARCHAR, .sysInfo = true},
{.name = "session_per_user", .bytes = 4, .type = TSDB_DATA_TYPE_INT, .sysInfo = true},

View file

@ -404,8 +404,8 @@ typedef struct {
typedef struct {
int64_t lastLoginTime; // in seconds
int64_t lastFailedLoginTime; // in seconds
int32_t lastLoginTime; // in seconds
int32_t lastFailedLoginTime; // in seconds
int32_t failedLoginCount;
} SLoginInfo;
@ -422,7 +422,7 @@ typedef struct {
typedef struct {
char pass[TSDB_PASSWORD_LEN];
int64_t setTime; // password set time, in seconds
int32_t setTime; // password set time, in seconds
} SUserPassword;
typedef struct {

View file

@ -56,6 +56,7 @@ int64_t mndGetUserTimeWhiteListVer(SMnode *pMnode, SUserObj *pUser);
void mndGetUserLoginInfo(const char *user, SLoginInfo *pLoginInfo);
void mndSetUserLoginInfo(const char *user, const SLoginInfo *pLoginInfo);
bool mndIsTotpEnabledUser(SUserObj *pUser);
#ifdef __cplusplus
}

View file

@ -29,6 +29,7 @@
#include "mndView.h"
#include "tglobal.h"
#include "tversion.h"
#include "totp.h"
typedef struct {
uint32_t id;
@ -293,12 +294,40 @@ static int32_t mndCountUserConns(SMnode *pMnode, const char *user) {
static bool constTimeEq(const char *a, const char *b, size_t len) {
volatile uint8_t res = 0;
for (size_t i = 0; i < len; i++) {
res |= a[i] ^ b[i];
static int32_t verifyPassword(SUserObj* pUser, const char* inputPass) {
int32_t code = 0;
const char* currPass = pUser->passwords[0].pass;
char pass[TSDB_PASSWORD_LEN] = {0};
(void)memcpy(pass, inputPass, TSDB_PASSWORD_LEN);
pass[TSDB_PASSWORD_LEN - 1] = 0;
if (pUser->passEncryptAlgorithm != 0) {
if (pUser->passEncryptAlgorithm != tsiEncryptPassAlgorithm) {
return TSDB_CODE_DNODE_INVALID_ENCRYPTKEY;
}
code = mndEncryptPass(pass, pUser->salt, NULL);
if (code != TSDB_CODE_SUCCESS) {
return code;
}
}
return res == 0;
// constant time comparison to prevent timing attack
volatile uint8_t res = 0;
for (size_t i = 0; i < sizeof(pass) - 1; i++) {
res |= pass[i] ^ currPass[i];
}
return (res == 0) ? TSDB_CODE_SUCCESS: TSDB_CODE_MND_AUTH_FAILURE;
}
static bool verifyTotp(SUserObj *pUser, int32_t totpCode) {
if (!mndIsTotpEnabledUser(pUser)) {
return true;
}
return taosVerifyTotpCode(pUser->totpsecret, sizeof(pUser->totpsecret), totpCode, 6, 1) != 0;
}
@ -336,11 +365,11 @@ static int32_t mndProcessConnectReq(SRpcMsg *pReq) {
goto _OVER;
}
int64_t now = taosGetTimestampSec();
int32_t now = taosGetTimestampSec();
if (pUser->passwordLifeTime > 0 && pUser->passwordGraceTime >= 0) {
int64_t lifeTime = now - pUser->passwords[0].setTime;
int64_t maxLifeTime = pUser->passwordLifeTime + pUser->passwordGraceTime;
if (lifeTime >= maxLifeTime) {
int32_t age = now - pUser->passwords[0].setTime;
int32_t maxLifeTime = pUser->passwordLifeTime + pUser->passwordGraceTime;
if (age >= maxLifeTime) {
mGError("user:%s, failed to login from %s since password expired", pReq->info.conn.user, ip);
code = TSDB_CODE_MND_USER_PASSWORD_EXPIRED;
goto _OVER;
@ -378,24 +407,22 @@ static int32_t mndProcessConnectReq(SRpcMsg *pReq) {
}
}
char tmpPass[TSDB_PASSWORD_LEN] = {0};
(void)memcpy(tmpPass, connReq.passwd, TSDB_PASSWORD_LEN);
tmpPass[TSDB_PASSWORD_LEN - 1] = 0;
if (pUser->passEncryptAlgorithm != 0) {
if (pUser->passEncryptAlgorithm != tsiEncryptPassAlgorithm) {
code = TSDB_CODE_DNODE_INVALID_ENCRYPTKEY;
goto _OVER;
}
TAOS_CHECK_GOTO(mndEncryptPass(tmpPass, pUser->salt, NULL), NULL, _OVER);
}
if (tsMndSkipGrant) {
loginInfo.lastLoginTime= now;
} else if (constTimeEq(tmpPass, pUser->passwords[0].pass, sizeof(tmpPass) - 1)) {
if (connReq.connType != CONN_TYPE__AUTH_TEST) {
mndSetUserLoginInfo(pReq->info.conn.user, &loginInfo);
}
} else if (!verifyTotp(pUser, connReq.totpCode)) {
mGError("user:%s, failed to login from %s since wrong TOTP code, input:%06d", pReq->info.conn.user, ip, connReq.totpCode);
code = TSDB_CODE_MND_WRONG_TOTP_CODE;
goto _OVER;
} else if ((code = verifyPassword(pUser, connReq.passwd)) == TSDB_CODE_SUCCESS) {
loginInfo.failedLoginCount = 0;
loginInfo.lastLoginTime= now;
} else {
if (connReq.connType != CONN_TYPE__AUTH_TEST) {
mndSetUserLoginInfo(pReq->info.conn.user, &loginInfo);
}
} else if (code == TSDB_CODE_MND_AUTH_FAILURE) {
mGError("user:%s, failed to login from %s since pass not match, input:%s", pReq->info.conn.user, ip, connReq.passwd);
if (pUser->failedLoginAttempts >= 0) {
if (loginInfo.failedLoginCount >= pUser->failedLoginAttempts) {
@ -405,11 +432,12 @@ static int32_t mndProcessConnectReq(SRpcMsg *pReq) {
loginInfo.failedLoginCount++;
loginInfo.lastFailedLoginTime = now;
}
code = TSDB_CODE_MND_AUTH_FAILURE;
}
mndSetUserLoginInfo(pReq->info.conn.user, &loginInfo);
if (code != 0) {
if (connReq.connType != CONN_TYPE__AUTH_TEST) {
mndSetUserLoginInfo(pReq->info.conn.user, &loginInfo);
}
goto _OVER;
} else {
mGError("user:%s, failed to login from %s since %s", pReq->info.conn.user, ip, tstrerror(code));
goto _OVER;
}
@ -430,6 +458,11 @@ static int32_t mndProcessConnectReq(SRpcMsg *pReq) {
TAOS_CHECK_GOTO(mndCheckDbPrivilege(pMnode, pReq->info.conn.user, MND_OPER_READ_OR_WRITE_DB, pDb), NULL, _OVER);
}
if (connReq.connType == CONN_TYPE__AUTH_TEST) {
code = 0;
goto _OVER;
}
pConn = mndCreateConn(pMnode, pReq->info.conn.user, connReq.connType, &pReq->info.conn.cliAddr, connReq.pid,
connReq.app, connReq.startTime, connReq.sVer);
if (pConn == NULL) {
@ -446,7 +479,6 @@ static int32_t mndProcessConnectReq(SRpcMsg *pReq) {
connectRsp.clusterId = pMnode->clusterId;
connectRsp.connId = pConn->id;
connectRsp.connType = connReq.connType;
connectRsp.mustChangePass = mndMustChangePassword(pUser) ? 1 : 0;
connectRsp.dnodeNum = mndGetDnodeSize(pMnode);
connectRsp.svrTimestamp = taosGetTimestampSec();
connectRsp.passVer = pUser->passVersion;

View file

@ -28,6 +28,7 @@
#include "mndTopic.h"
#include "mndTrans.h"
#include "tbase64.h"
#include "totp.h"
// clang-format on
@ -956,7 +957,7 @@ static void dropOldPasswords(SUserObj *pUser) {
reuseMax = 1; // keep at least one password
}
int64_t now = taosGetTimestampSec();
int32_t now = taosGetTimestampSec();
int32_t index = reuseMax;
while(index < pUser->numOfPasswords) {
SUserPassword *pPass = &pUser->passwords[index];
@ -1208,7 +1209,7 @@ SSdbRaw *mndUserActionEncode(SUserObj *pUser) {
SDB_SET_INT32(pRaw, dataPos, pUser->numOfPasswords, _OVER)
for (int32_t i = 0; i < pUser->numOfPasswords; i++) {
SDB_SET_BINARY(pRaw, dataPos, pUser->passwords[i].pass, sizeof(pUser->passwords[i].pass), _OVER)
SDB_SET_INT64(pRaw, dataPos, pUser->passwords[i].setTime, _OVER)
SDB_SET_INT32(pRaw, dataPos, pUser->passwords[i].setTime, _OVER)
}
SDB_SET_BINARY(pRaw, dataPos, pUser->salt, sizeof(pUser->salt), _OVER)
@ -1449,7 +1450,7 @@ static SSdbRow *mndUserActionDecode(SSdbRaw *pRaw) {
}
for (int32_t i = 0; i < pUser->numOfPasswords; ++i) {
SDB_GET_BINARY(pRaw, dataPos, pUser->passwords[i].pass, sizeof(pUser->passwords[i].pass), _OVER);
SDB_GET_INT64(pRaw, dataPos, &pUser->passwords[i].setTime, _OVER);
SDB_GET_INT32(pRaw, dataPos, &pUser->passwords[i].setTime, _OVER);
}
SDB_GET_BINARY(pRaw, dataPos, pUser->salt, sizeof(pUser->salt), _OVER)
}
@ -1458,7 +1459,7 @@ static SSdbRow *mndUserActionDecode(SSdbRaw *pRaw) {
SDB_GET_INT64(pRaw, dataPos, &pUser->createdTime, _OVER)
SDB_GET_INT64(pRaw, dataPos, &pUser->updateTime, _OVER)
if (sver < USER_VER_SUPPORT_ADVANCED_SECURITY) {
pUser->passwords[0].setTime = pUser->updateTime / 1000;
pUser->passwords[0].setTime = (int32_t)(pUser->updateTime / 1000);
}
SDB_GET_INT8(pRaw, dataPos, &pUser->superUser, _OVER)
@ -2165,7 +2166,10 @@ static int32_t mndCreateUser(SMnode *pMnode, char *acct, SCreateUserReq *pCreate
tstrncpy(userObj.user, pCreate->user, TSDB_USER_LEN);
tstrncpy(userObj.acct, acct, TSDB_USER_LEN);
if (pCreate->totpseed[0] != 0) {
// TODO: generate totp seed
int len = taosGenerateTotpSecret(pCreate->totpseed, 0, userObj.totpsecret, sizeof(userObj.totpsecret));
if (len < 0) {
TAOS_CHECK_GOTO(TSDB_CODE_PAR_INVALID_OPTION_VALUE, &lino, _OVER);
}
}
userObj.createdTime = taosGetTimestampMs();
@ -2333,7 +2337,6 @@ _OVER:
}
static int32_t mndCheckPasswordFmt(const char *pwd) {
if (strcmp(pwd, "taosdata") == 0) {
return 0;
@ -2357,26 +2360,26 @@ static int32_t mndCheckPasswordFmt(const char *pwd) {
return TSDB_CODE_PAR_NAME_OR_PASSWD_TOO_LONG;
}
int32_t upper = 0, lower = 0, number = 0, special = 0;
for (int32_t i = 0; i < len; ++i) {
if (taosIsBigChar(pwd[i])) {
upper = 1;
} else if (taosIsSmallChar(pwd[i])) {
lower = 1;
} else if (taosIsNumberChar(pwd[i])) {
number = 1;
} else if (taosIsSpecialChar(pwd[i])) {
special = 1;
} else {
return TSDB_CODE_MND_INVALID_PASS_FORMAT;
}
if (taosIsComplexString(pwd)) {
return 0;
}
if (upper + lower + number + special < 3) {
return TSDB_CODE_MND_INVALID_PASS_FORMAT;
return TSDB_CODE_MND_INVALID_PASS_FORMAT;
}
static int32_t mndCheckTotpSeedFmt(const char *seed) {
int32_t len = strlen(seed);
if (len < TSDB_USER_TOTPSEED_MIN_LEN) {
return TSDB_CODE_PAR_OPTION_VALUE_TOO_SHORT;
}
return 0;
if (taosIsComplexString(seed)) {
return 0;
}
return TSDB_CODE_PAR_INVALID_OPTION_VALUE;
}
@ -2565,6 +2568,11 @@ static int32_t mndProcessCreateUserReq(SRpcMsg *pReq) {
TAOS_CHECK_GOTO(code, &lino, _OVER);
}
if (createReq.totpseed[0] != 0) {
code = mndCheckTotpSeedFmt(createReq.totpseed);
TAOS_CHECK_GOTO(code, &lino, _OVER);
}
code = mndAcquireUser(pMnode, createReq.user, &pUser);
if (pUser != NULL) {
TAOS_CHECK_GOTO(TSDB_CODE_MND_USER_ALREADY_EXIST, &lino, _OVER);
@ -3201,7 +3209,11 @@ static int32_t mndProcessAlterUserReq(SRpcMsg *pReq) {
}
if (alterReq.hasTotpseed) {
// TODO: totp seed to secret
if (alterReq.totpseed[0] == 0) { // clear totp secret
memset(newUser.totpsecret, 0, sizeof(newUser.totpsecret));
} else if (taosGenerateTotpSecret(alterReq.totpseed, 0, newUser.totpsecret, sizeof(newUser.totpsecret)) < 0) {
TAOS_CHECK_GOTO(TSDB_CODE_PAR_INVALID_OPTION_VALUE, &lino, _OVER);
}
}
if (alterReq.hasEnable) {
@ -3739,6 +3751,17 @@ _exit:
TAOS_RETURN(code);
}
bool mndIsTotpEnabledUser(SUserObj *pUser) {
for (int32_t i = 0; i < sizeof(pUser->totpsecret); i++) {
if (pUser->totpsecret[i] != 0) {
return true;
}
}
return false;
}
static int32_t mndRetrieveUsers(SRpcMsg *pReq, SShowObj *pShow, SSDataBlock *pBlock, int32_t rows) {
SMnode *pMnode = pReq->info.node;
SSdb *pSdb = pMnode->pSdb;
@ -3783,6 +3806,11 @@ static int32_t mndRetrieveUsers(SRpcMsg *pReq, SShowObj *pShow, SSDataBlock *pBl
pColInfo = taosArrayGet(pBlock->pDataBlock, cols);
COL_DATA_SET_VAL_GOTO((const char *)&pUser->createdTime, false, pUser, pShow->pIter, _exit);
cols++;
pColInfo = taosArrayGet(pBlock->pDataBlock, cols);
flag = mndIsTotpEnabledUser(pUser) ? 1 : 0;
COL_DATA_SET_VAL_GOTO((const char *)&flag, false, pUser, pShow->pIter, _exit);
cols++;
int32_t tlen = convertIpWhiteListToStr(pUser, &buf);
@ -3886,6 +3914,11 @@ static int32_t mndRetrieveUsersFull(SRpcMsg *pReq, SShowObj *pShow, SSDataBlock
pColInfo = taosArrayGet(pBlock->pDataBlock, cols);
COL_DATA_SET_VAL_GOTO((const char *)&pUser->createdTime, false, pUser, pShow->pIter, _exit);
cols++;
pColInfo = taosArrayGet(pBlock->pDataBlock, cols);
flag = mndIsTotpEnabledUser(pUser) ? 1 : 0;
COL_DATA_SET_VAL_GOTO((const char *)&flag, false, pUser, pShow->pIter, _exit);
cols++;
pColInfo = taosArrayGet(pBlock->pDataBlock, cols);
COL_DATA_SET_VAL_GOTO((const char *)&pUser->changePass, false, pUser, pShow->pIter, _exit);

View file

@ -6751,6 +6751,50 @@ const SBuiltinFuncDefinition funcMgtBuiltins[] = {
.sprocessFunc = NULL,
.finalizeFunc = NULL
},
{
.name = "generate_totp_secret",
.type = FUNCTION_TYPE_GENERATE_TOTP_SECRET,
.classification = FUNC_MGT_SCALAR_FUNC | FUNC_MGT_STRING_FUNC,
.parameters = {.minParamNum = 1,
.maxParamNum = 1,
.paramInfoPattern = 1,
.inputParaInfo[0][0] = {.isLastParam = true,
.startParam = 1,
.endParam = 1,
.validDataType = FUNC_PARAM_SUPPORT_VARCHAR_TYPE | FUNC_PARAM_SUPPORT_NCHAR_TYPE,
.validNodeType = FUNC_PARAM_SUPPORT_EXPR_NODE,
.paramAttribute = FUNC_PARAM_NO_SPECIFIC_ATTRIBUTE,
.valueRangeFlag = FUNC_PARAM_NO_SPECIFIC_VALUE,},
.outputParaInfo = {.validDataType = FUNC_PARAM_SUPPORT_VARCHAR_TYPE | FUNC_PARAM_SUPPORT_NCHAR_TYPE}},
.translateFunc = translateOutFirstIn,
.getEnvFunc = NULL,
.initFunc = NULL,
.sprocessFunc = generateTotpSecretFunction,
.finalizeFunc = NULL,
},
{
// this function generates TOTP code based on the TOTP secret,
// it is not documented and only used for testing purpose to verify the correctness of TOTP code generation.
.name = "generate_totp_code",
.type = FUNCTION_TYPE_GENERATE_TOTP_CODE,
.classification = FUNC_MGT_SCALAR_FUNC | FUNC_MGT_STRING_FUNC,
.parameters = {.minParamNum = 1,
.maxParamNum = 1,
.paramInfoPattern = 1,
.inputParaInfo[0][0] = {.isLastParam = true,
.startParam = 1,
.endParam = 1,
.validDataType = FUNC_PARAM_SUPPORT_VARCHAR_TYPE | FUNC_PARAM_SUPPORT_NCHAR_TYPE,
.validNodeType = FUNC_PARAM_SUPPORT_EXPR_NODE,
.paramAttribute = FUNC_PARAM_NO_SPECIFIC_ATTRIBUTE,
.valueRangeFlag = FUNC_PARAM_NO_SPECIFIC_VALUE,},
.outputParaInfo = {.validDataType = FUNC_PARAM_SUPPORT_VARCHAR_TYPE}},
.translateFunc = translateOutFirstIn,
.getEnvFunc = NULL,
.initFunc = NULL,
.sprocessFunc = generateTotpCodeFunction,
.finalizeFunc = NULL,
},
};
// clang-format on

View file

@ -112,6 +112,7 @@ user_enabled(A) ::= ENABLE NK_INTEGER(B).
%type user_option { SUserOptions* }
user_option(A) ::= TOTPSEED NK_STRING(B). { A = mergeUserOptions(pCxt, NULL, NULL); setUserOptionsTotpseed(pCxt, A, &B); }
user_option(A) ::= TOTPSEED NULL. { A = mergeUserOptions(pCxt, NULL, NULL); setUserOptionsTotpseed(pCxt, A, NULL); }
user_option(A) ::= user_enabled(B). { A = mergeUserOptions(pCxt, NULL, NULL); A->enable = B; A->hasEnable = true; }
user_option(A) ::= SYSINFO NK_INTEGER(B). { A = mergeUserOptions(pCxt, NULL, NULL); A->sysinfo = taosStr2Int8(B.z, NULL, 10); A->hasSysinfo = true; }
user_option(A) ::= IS_IMPORT NK_INTEGER(B). { A = mergeUserOptions(pCxt, NULL, NULL); A->isImport = taosStr2Int8(B.z, NULL, 10); A->hasIsImport = true; }

View file

@ -121,31 +121,11 @@ static bool isValidSimplePassword(const char* password) {
static bool isComplexString(const char* str) {
int hasUpper = 0, hasLower = 0, hasDigit = 0, hasSpecial = 0;
for (char c = *str; c != 0; c = *(++str)) {
if (taosIsBigChar(c)) {
hasUpper = 1;
} else if (taosIsSmallChar(c)) {
hasLower = 1;
} else if (taosIsNumberChar(c)) {
hasDigit = 1;
} else if (taosIsSpecialChar(c)) {
hasSpecial = 1;
}
}
return (hasUpper + hasLower + hasDigit + hasSpecial) >= 3;
}
static bool isValidStrongPassword(const char* password) {
if (strcmp(password, "taosdata") == 0) {
return true;
}
return isComplexString(password);
return taosIsComplexString(password);
}
@ -4275,6 +4255,11 @@ void setUserOptionsTotpseed(SAstCreateContext* pCxt, SUserOptions* pUserOptions,
}
pUserOptions->hasTotpseed = true;
if (pTotpseed == NULL) { // clear TOTP secret
memset(pUserOptions->totpseed, 0, sizeof(pUserOptions->totpseed));
return;
}
if (pTotpseed->n >= sizeof(pUserOptions->totpseed) * 2) {
pCxt->errCode = generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_PAR_OPTION_VALUE_TOO_LONG, "TOTPSEED", sizeof(pUserOptions->totpseed));
return;
@ -4347,7 +4332,7 @@ static bool isValidUserOptions(SAstCreateContext* pCxt, const SUserOptions* opts
return false;
}
if (opts->hasTotpseed && !isComplexString(opts->totpseed)) {
if (opts->hasTotpseed && opts->totpseed[0] != 0 && !taosIsComplexString(opts->totpseed)) {
pCxt->errCode = generateSyntaxErrMsg(&pCxt->msgBuf, TSDB_CODE_PAR_INVALID_OPTION_VALUE, "TOTPSEED");
return false;
}

View file

@ -11,6 +11,7 @@
#include "tjson.h"
#include "ttime.h"
#include "tcompare.h"
#include "totp.h"
typedef float (*_float_fn)(float);
typedef float (*_float_fn_2)(float, float);
@ -1723,6 +1724,166 @@ _return:
return code;
}
static int32_t base32Encode(const uint8_t *in, int32_t inLen, char *out) {
int buffer = 0, bits = 0;
int outLen = 0;
// process all input bytes
for (int i = 0; i < inLen; i++) {
buffer = (buffer << 8) | in[i];
bits += 8;
while (bits >= 5) {
int v = (buffer >> (bits - 5)) & 0x1F;
out[outLen++] = (v >= 26) ? (v - 26 + '2') : (v + 'A');
bits -= 5;
}
}
// process remaining bits
if (bits > 0) {
int v = (buffer << (5 - bits)) & 0x1F;
out[outLen++] = (v >= 26) ? (v - 26 + '2') : (v + 'A');
}
out[outLen] = '\0';
return outLen;
}
int32_t generateTotpSecretFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput) {
SColumnInfoData *pInputData = pInput->columnData;
SColumnInfoData *pOutputData = pOutput->columnData;
char secret[64] = {0};
int32_t bufLen = (sizeof(secret) * 8) / 5 + 4 + VARSTR_HEADER_SIZE + 1;
char *pOutputBuf = taosMemoryMalloc(bufLen);
if (!pOutputBuf) {
qError("generate_totp_secret function alloc memory failed");
return terrno;
}
for (int32_t i = 0; i < pInput->numOfRows; ++i) {
if (colDataIsNull_s(pInputData, i)) {
colDataSetNULL(pOutputData, i);
continue;
}
char *input = colDataGetData(pInput[0].columnData, i);
int len = taosGenerateTotpSecret(varDataVal(input), varDataLen(input), secret, sizeof(secret));
if (len < 0) {
taosMemoryFree(pOutputBuf);
SCL_ERR_RET(TSDB_CODE_INTERNAL_ERROR);
}
char *output = pOutputBuf;
len = base32Encode(secret, len, varDataVal(output));
varDataSetLen(output, len);
int32_t code = colDataSetVal(pOutputData, i, output, false);
if (TSDB_CODE_SUCCESS != code) {
taosMemoryFree(pOutputBuf);
SCL_ERR_RET(code);
}
}
pOutput->numOfRows = pInput->numOfRows;
taosMemoryFree(pOutputBuf);
return TSDB_CODE_SUCCESS;
}
static int32_t base32Decode(const char *in, int32_t inLen, uint8_t *out) {
int buffer = 0, bits = 0;
int32_t outLen = 0;
for (int32_t i = 0; i < inLen; i++) {
char c = in[i];
if (c >= 'a' && c <= 'z') {
c -= 'a';
} else if (c >= 'A' && c <= 'Z') {
c -= 'A';
} else if (c >= '2' && c <= '7') {
c = c - '2' + 26;
} else if (c == '=') {
break; // padding character
} else {
return -1; // invalid character
}
buffer = (buffer << 5) | c;
bits += 5;
if (bits >= 8) {
out[outLen++] = (buffer >> (bits - 8)) & 0xFF;
bits -= 8;
}
}
return outLen; // success
}
// this function generates TOTP code based on the TOTP secret,
// it is not documented and only used for testing purpose to verify the correctness of
// TOTP code generation.
int32_t generateTotpCodeFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput) {
SColumnInfoData *pInputData = pInput->columnData;
SColumnInfoData *pOutputData = pOutput->columnData;
char *output = taosMemoryMalloc(VARSTR_HEADER_SIZE + 6 + 1);
if (!output) {
qError("generate_totp code function alloc memory failed");
return terrno;
}
uint8_t secret[64] = {0};
int32_t secretLen = 0;
for (int32_t i = 0; i < pInput->numOfRows; ++i) {
if (colDataIsNull_s(pInputData, i)) {
colDataSetNULL(pOutputData, i);
continue;
}
char *in = colDataGetData(pInput[0].columnData, i);
if (varDataLen(in) > 100) {
colDataSetNULL(pOutputData, i);
qError("the %d secret is too long", i);
continue;
}
secretLen = base32Decode(varDataVal(in), varDataLen(in), secret);
if (secretLen <= 0) {
qError("failed to decode the %d base32 secret", i);
colDataSetNULL(pOutputData, i);
continue;
}
int64_t totp = taosGenerateTotpCode(secret, secretLen, 6);
if (totp < 0) {
qError("failed to generate the %d totp code", i);
colDataSetNULL(pOutputData, i);
continue;
}
int len = taosFormatTotp(totp, 6, varDataVal(output), 7);
varDataSetLen(output, len);
int32_t code = colDataSetVal(pOutputData, i, output, false);
if (TSDB_CODE_SUCCESS != code) {
taosMemoryFree(output);
SCL_ERR_RET(code);
}
}
pOutput->numOfRows = pInput->numOfRows;
taosMemoryFree(output);
return TSDB_CODE_SUCCESS;
}
int32_t md5Function(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput) {
SColumnInfoData *pInputData = pInput->columnData;
SColumnInfoData *pOutputData = pOutput->columnData;

View file

@ -10,6 +10,9 @@ if(TD_LINUX)
DEP_ext_xxhash(util)
DEP_ext_lzma2(util)
endif()
if (NOT ${TD_WINDOWS})
DEP_ext_ssl(util)
endif()
DEP_ext_geos(util)
if(${BUILD_PCRE2}) # {
DEP_ext_pcre2(util)

View file

@ -178,6 +178,7 @@ TAOS_DEFINE_ERROR(TSDB_CODE_PAR_INVALID_COLUMN_REF, "Invalid column refere
TAOS_DEFINE_ERROR(TSDB_CODE_PAR_INVALID_SLIDING_OFFSET, "Invalid sliding offset")
TAOS_DEFINE_ERROR(TSDB_CODE_PAR_INVALID_INTERVAL_OFFSET, "Invalid interval offset")
TAOS_DEFINE_ERROR(TSDB_CODE_NOT_SUPPORTTED_IN_WINDOWS, "Operation not supported in windows")
TAOS_DEFINE_ERROR(TSDB_CODE_TSC_INVALID_TOTP_CODE, "Invalid TOTP code")
TAOS_DEFINE_ERROR(TSDB_CODE_TSC_INTERNAL_ERROR, "Internal error")
@ -235,7 +236,7 @@ TAOS_DEFINE_ERROR(TSDB_CODE_MND_INVALID_ALTER_OPER, "Invalid alter operati
TAOS_DEFINE_ERROR(TSDB_CODE_MND_AUTH_FAILURE, "Authentication failure")
TAOS_DEFINE_ERROR(TSDB_CODE_MND_PRIVILEDGE_EXIST, "User already have this priviledge")
TAOS_DEFINE_ERROR(TSDB_CODE_MND_USER_PASSWORD_REUSE, "Password reuse detected")
//TAOS_DEFINE_ERROR(TSDB_CODE_MND_USER_TIME_RANGE_CONFLICT, "Date time black/white list conflict")
TAOS_DEFINE_ERROR(TSDB_CODE_MND_WRONG_TOTP_CODE, "Wrong TOTP code")
TAOS_DEFINE_ERROR(TSDB_CODE_MND_TOO_MANY_USER_IP_RANGE, "Too many ranges in IP white list")
TAOS_DEFINE_ERROR(TSDB_CODE_MND_TOO_MANY_USER_TIME_RANGE, "Too many ranges in date time white list")

135
source/util/src/totp.c Normal file
View file

@ -0,0 +1,135 @@
/*
* Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
*
* This program is free software: you can use, redistribute, and/or modify
* it under the terms of the GNU Affero General Public License, version 3
* or later ("AGPL"), as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define _DEFAULT_SOURCE
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include "ttime.h"
#ifdef WINDOWS
static int32_t doGenerateTotp(const uint8_t *secret, size_t secretLen, uint64_t tm, int digits) {
return -1;
}
int taosGenerateTotpSecret(const char *seed, size_t seedLen, uint8_t *secret, size_t secretLen) {
return 0;
}
#else // WINDOWS
// for OpenSSL HMAC functions
#include <openssl/evp.h>
#include <openssl/hmac.h>
// doGenerateTotp generates a TOTP code based on the provided secret and time.
// secret is a byte array, not a base32 string
static int32_t doGenerateTotp(const uint8_t *secret, size_t secretLen, uint64_t tm, int digits) {
if (secretLen < 16) {
return -1; // secret is too short
}
// convert timestamp to big-endian if system is little-endian
uint32_t endianness = 0xdeadbeef;
if ((*(const uint8_t *)&endianness) == 0xef) {
tm = ((tm & 0x00000000ffffffff) << 32) | ((tm & 0xffffffff00000000) >> 32);
tm = ((tm & 0x0000ffff0000ffff) << 16) | ((tm & 0xffff0000ffff0000) >> 16);
tm = ((tm & 0x00ff00ff00ff00ff) << 8) | ((tm & 0xff00ff00ff00ff00) >> 8);
};
// calculate HMAC-SHA1
uint8_t hmacResult[EVP_MAX_MD_SIZE];
unsigned int hmacLen;
if (HMAC(EVP_sha1(), secret, secretLen, (const uint8_t *)&tm, sizeof(tm), hmacResult, &hmacLen) == NULL) {
return -1; // HMAC calculation failed
}
// use the lower 4 bits of the last byte as offset
int offset = hmacResult[hmacLen - 1] & 0x0F;
int32_t binary = (hmacResult[offset] & 0x7F) << 24;
binary |= (hmacResult[offset + 1] & 0xFF) << 16;
binary |= (hmacResult[offset + 2] & 0xFF) << 8;
binary |= hmacResult[offset + 3] & 0xFF;
return binary % (int32_t)pow(10, digits);
}
// generate TOTP secret from seed
// secret is a byte array, not a base32 string
int taosGenerateTotpSecret(const char *seed, size_t seedLen, uint8_t *secret, size_t secretLen) {
// TOTP secret requires at least 16 characters for adequate security
if (secretLen < 16) {
return 0;
}
if (seedLen == 0) {
seedLen = strlen(seed);
}
// calculate HMAC-SHA1
uint8_t hmacResult[EVP_MAX_MD_SIZE];
unsigned int hmacLen;
if (HMAC(EVP_sha256(), NULL, 0, seed, seedLen, hmacResult, &hmacLen) == NULL) {
return 0; // HMAC calculation failed
}
if (hmacLen > secretLen) {
hmacLen = secretLen;
}
(void)memcpy(secret, hmacResult, hmacLen);
return hmacLen;
}
#endif // WINDOWS
// formatTotp formats the TOTP code with leading zeros to match the specified digit length.
int taosFormatTotp(int32_t totp, int digits, char *buffer, size_t size) {
return snprintf(buffer, size, "%0*d", digits, totp);
}
// generate TOTP code for given secret at current time
// secret is a byte array, not a base32 string
int32_t taosGenerateTotpCode(const uint8_t *secret, size_t secretLen, int digits) {
if (secretLen < 16) {
return -1; // secret is too short
}
uint64_t now = taosGetTimestampSec() / 30;
return doGenerateTotp(secret, secretLen, now, digits);
}
// verify TOTP code for given secret at current time with allowed window
// secret is a byte array, not a base32 string
int taosVerifyTotpCode(const uint8_t *secret, size_t secretLen, int32_t userCode, int digits, int window) {
if (secretLen < 16) {
return 0; // secret is too short
}
uint64_t now = taosGetTimestampSec() / 30;
for (int i = -window; i <= window; i++) {
if (doGenerateTotp(secret, secretLen, now + i, digits) == userCode) {
return 1;
}
}
return 0;
}

View file

@ -588,6 +588,29 @@ bool taosIsSpecialChar(char c) {
}
}
// check if the string is a complex string, a complex string contains
// at least 3 types of characters: upper, lower, digit, special
bool taosIsComplexString(const char* str) {
int hasUpper = 0, hasLower = 0, hasDigit = 0, hasSpecial = 0;
for (char c = *str; c != 0; c = *(++str)) {
if (taosIsBigChar(c)) {
hasUpper = 1;
} else if (taosIsSmallChar(c)) {
hasLower = 1;
} else if (taosIsNumberChar(c)) {
hasDigit = 1;
} else if (taosIsSpecialChar(c)) {
hasSpecial = 1;
} else {
return false;
}
}
return (hasUpper + hasLower + hasDigit + hasSpecial) >= 3;
}
void tTrimMountPrefix(char *fullName) {
if (fullName == NULL) {
return;

View file

@ -100,6 +100,7 @@ TSDB_CODE_TSC_ENCODE_PARAM_NULL = 0x80000232
TSDB_CODE_TSC_COMPRESS_PARAM_ERROR = 0x80000233
TSDB_CODE_TSC_COMPRESS_LEVEL_ERROR = 0x80000234
TSDB_CODE_TSC_STMT_BIND_NUMBER_ERROR = 0x80000236
TSDB_CODE_TSC_INVALID_TOTP_CODE = 0x80000238
TSDB_CODE_TSC_INTERNAL_ERROR = 0x800002FF
TSDB_CODE_MND_REQ_REJECTED = 0x80000300
TSDB_CODE_MND_NO_RIGHTS = 0x80000303
@ -145,6 +146,7 @@ TSDB_CODE_MND_INVALID_ALTER_OPER = 0x80000356
TSDB_CODE_MND_AUTH_FAILURE = 0x80000357
TSDB_CODE_MND_PRIVILEDGE_EXIST = 0x80000359
TSDB_CODE_MND_USER_PASSWORD_REUSE = 0x8000035A
TSDB_CODE_MND_WRONG_TOTP_CODE = 0x8000035B
TSDB_CODE_MND_TOO_MANY_USER_IP_RANGE = 0x8000035C
TSDB_CODE_MND_TOO_MANY_USER_TIME_RANGE = 0x8000035D
TSDB_CODE_MND_STB_ALREADY_EXIST = 0x80000360

View file

@ -20,7 +20,7 @@ ignoreCodes = [
'0x80000129', '0x8000012C', '0x8000012D', '0x8000012E', '0x8000012F', '0x80000136', '0x80000137', '0x80000138', '0x80000139', '0x8000013A',
'0x8000013B', '0x80000200', '0x80000201', '0x80000202', '0x80000203', '0x80000204', '0x80000205', '0x80000206', '0x8000020B', '0x8000020E',
'0x80000210', '0x80000212', '0x80000214', '0x80000215', '0x80000217', '0x8000021B', '0x8000021C', '0x8000021D', '0x8000021E', '0x80000220',
'0x80000221', '0x8000022C', '0x80000232', '0x80000233', '0x80000234', '0x80000235', '0x80000236', '0x800002FF', '0x80000300', '0x80000316',
'0x80000221', '0x8000022C', '0x80000231', '0x80000232', '0x80000233', '0x80000234', '0x80000235', '0x80000236', '0x800002FF', '0x80000300', '0x80000316',
'0x80000317', '0x80000338', '0x80000339', '0x8000033F', '0x80000343', '0x80000345', '0x80000356', '0x80000359', '0x8000035A', '0x8000035B',
'0x8000035C', '0x8000035D', '0x80000362', '0x8000038C', '0x8000038E', '0x8000039B', '0x8000039C', '0x8000039D', '0x8000039E', '0x800003A6',
'0x800003A7', '0x800003AA', '0x800003AB', '0x800003AC', '0x800003AD', '0x800003B0', '0x800003B2', '0x800003B4', '0x800003B5', '0x800003BA',