Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ jobs:
MYSQL_DBNAME: ${{ secrets.MYSQL_DBNAME }}
REDIS_HOST: ${{ secrets.REDIS_HOST }}
REDIS_DBNAME: ${{ secrets.REDIS_DBNAME }}
ADMIN_PASSWD: ${{ secrets.ADMIN_PASSWD }}
GITHUB_CLIENT_ID: ${{ secrets.OAUTH_GITHUB_CLIENT_ID }}
GITHUB_CLIENT_SECRET: ${{ secrets.OAUTH_GITHUB_CLIENT_SECRET }}
GITHUB_CALLBACK_URL: ${{ secrets.OAUTH_GITHUB_CALLBACK_URL }}
Expand All @@ -122,6 +123,7 @@ jobs:
echo "MYSQL_DBNAME=${MYSQL_DBNAME:-techhaven}"
echo "REDIS_HOST=${REDIS_HOST:-127.0.0.1:6379}"
echo "REDIS_DBNAME=${REDIS_DBNAME:-blog}"
echo "ADMIN_PASSWD=${ADMIN_PASSWD:-}"
echo "GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID}"
echo "GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET}"
echo "GITHUB_CALLBACK_URL=${GITHUB_CALLBACK_URL}"
Expand Down
3 changes: 3 additions & 0 deletions CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@

> `bin/conf/` 下只有 `.yml` 会被框架加载;`.tpl` / `.env` / `.md` 不会被加载。

> **超级管理员引导**:仅当库中不存在 `admin.email` 对应的账号时才会创建管理员,初始密码取
> `<ENV>.env` 中的 `ADMIN_PASSWD`(无默认密码)。已存在的管理员密码不会被启动流程覆盖。

## 2. 切换环境

```sh
Expand Down
6 changes: 4 additions & 2 deletions bin/conf/TechHaven.yml.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ ai:
model: glm-4.7-flash
api_key: # system default AI API key, used when user has not configured their own AI settings

# 默认超级管理员:服务启动时若不存在管理员则自动创建,用于首次登录并配置 SMTP 等系统设置
# 默认超级管理员:服务启动时若该邮箱对应的管理员不存在则自动创建,用于首次登录并配置 SMTP 等系统设置。
# 出于安全考虑不提供默认密码:首次部署必须在 <ENV>.env 中显式设置 ADMIN_PASSWD,
# 已存在的管理员密码不会被启动流程覆盖。
admin:
account: admin
passwd: admin123456
passwd: ${ADMIN_PASSWD}
email: admin@example.com

# 分词字典配置
Expand Down
3 changes: 3 additions & 0 deletions bin/conf/dev.env
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ REDIS_DBNAME=blog_dev
REDIS_HOST=127.0.0.1:6379
DATA_PATH=""

# 开发环境超级管理员初始密码(仅首次创建管理员时使用)
ADMIN_PASSWD=admin123456

# GitHub OAuth 登录(开发环境)
# 在 https://github.com/settings/developers 新建 OAuth App 后填入 Client ID / Secret
# callback_url 必须与 OAuth App 的 Authorization callback URL 完全一致
Expand Down
4 changes: 4 additions & 0 deletions bin/conf/prod.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ MYSQL_DBNAME=techhaven
REDIS_HOST=127.0.0.1:6379
REDIS_DBNAME=blog

# 首次部署的超级管理员初始密码(仅当库中不存在 admin.email 对应账号时使用)
# 请填写强密码;已存在的管理员密码不会被覆盖
ADMIN_PASSWD=

# GitHub OAuth 登录(生产环境)
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
Expand Down
27 changes: 22 additions & 5 deletions src/manager/user_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ static void setUserCache(const data::UserInfo::ptr& info)

static chen::ConfigVar<std::string>::ptr g_admin_account = chen::Config::Lookup("admin.account", std::string("admin"), "default super admin account");
static chen::ConfigVar<std::string>::ptr g_admin_passwd =
chen::Config::Lookup("admin.passwd", std::string("admin123456"), "default super admin password");
chen::Config::Lookup("admin.passwd", std::string(""), "bootstrap super admin password (required only on first seed)");
static chen::ConfigVar<std::string>::ptr g_admin_email =
chen::Config::Lookup("admin.email", std::string("admin@example.com"), "default super admin email");

Expand Down Expand Up @@ -206,18 +206,31 @@ void UserManager::ensureSuperAdmin()
std::string account = g_admin_account->getValue();
std::string passwd = g_admin_passwd->getValue();
std::string email = g_admin_email->getValue();
if (account.empty() || passwd.empty() || email.empty()) {
ERROR(logger) << "admin account or password empty, skip seeding super admin";
if (email.empty()) {
ERROR(logger) << "admin.email empty, skip seeding super admin";
return;
}

auto info = data::UserInfoDao::QueryByEmail(email, db);
bool is_new = false;
if (!info) {
// 仅在首次创建时要求配置初始密码,已有管理员不会被重置密码
if (account.empty() || passwd.empty()) {
ERROR(logger) << "super admin not found and admin.account/admin.passwd empty, "
<< "please configure them to bootstrap the first admin";
return;
}
std::string passwd_hash = HashPassword(passwd);
if (passwd_hash.empty()) {
ERROR(logger) << "hash super admin password failed, skip seeding";
return;
}
info.reset(new data::UserInfo);
info->setPasswd(chen::EncryptorUtil::MD5(passwd));
info->setPasswd(passwd_hash);
info->setName(account);
info->setAccount(account);
info->setEmail(email);
is_new = true;
}

info->setRole(Role::ADMIN);
Expand All @@ -230,7 +243,11 @@ void UserManager::ensureSuperAdmin()
}
setUserCache(info);

INFO(logger) << "seeded super admin account=" << account << " email=" << email;
if (is_new) {
INFO(logger) << "seeded super admin account=" << account << " email=" << email;
} else {
INFO(logger) << "super admin ensured account=" << info->getAccount() << " email=" << email;
}
}

blog::data::UserInfo::ptr UserManager::getByName(const std::string& v)
Expand Down
27 changes: 8 additions & 19 deletions src/servlets/user/user_admin_create_servlet.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace servlet {
static chen::Logger::ptr logger = LOG_ROOT();

UserAdminCreateServlet::UserAdminCreateServlet()
: BlogLoginedServlet("UserAdminCreateServlet")
: BlogAdminServlet("UserAdminCreateServlet")
{
}

Expand All @@ -28,23 +28,6 @@ int32_t UserAdminCreateServlet::handle(
DEFINE_AND_CHECK_TYPE(result, int32_t, role, "role");
DEFINE_AND_CHECK_TYPE(result, int32_t, state, "state");

int64_t uid = getUserId(request);
if (!uid) {
result->setErrno(errcode::NOT_LOGIN);
break;
}
auto current_user = UserMgr::GetInstance()->get(uid);
if (!current_user) {
result->setErrno(errcode::USER_NOT_FOUND);
break;
}
int32_t user_role = current_user->getRole();

if (user_role != UserManager::Role::ADMIN) {
result->setErrno(errcode::ACCESS_DENIED);
break;
}

if (account.empty() || passwd.empty()) {
result->setErrno(errcode::PARAM_MISSING, "account and password required");
break;
Expand Down Expand Up @@ -83,11 +66,17 @@ int32_t UserAdminCreateServlet::handle(
result->setErrno(errcode::DB_CONNECTION_FAILED);
break;
}
std::string passwd_hash = HashPassword(passwd);
if (passwd_hash.empty()) {
result->setErrno(errcode::INTERNAL_ERROR);
break;
}

chen::ITransaction::ptr trans = db->openTransaction();
data::UserInfo::ptr info(new data::UserInfo);
info->setAccount(account);
info->setEmail(email);
info->setPasswd(chen::EncryptorUtil::MD5(passwd));
info->setPasswd(passwd_hash);
info->setRole(role);
info->setState(state);
info->setName(account);
Expand Down
2 changes: 1 addition & 1 deletion src/servlets/user/user_admin_create_servlet.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace blog {
namespace servlet {

class UserAdminCreateServlet : public BlogLoginedServlet {
class UserAdminCreateServlet : public BlogAdminServlet {
public:
typedef std::shared_ptr<UserAdminCreateServlet> ptr;
UserAdminCreateServlet();
Expand Down
19 changes: 1 addition & 18 deletions src/servlets/user/user_admin_delete_servlet.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace servlet {
static chen::Logger::ptr logger = LOG_ROOT();

UserAdminDeleteServlet::UserAdminDeleteServlet()
: BlogLoginedServlet("UserAdminDeleteServlet")
: BlogAdminServlet("UserAdminDeleteServlet")
{
}

Expand All @@ -25,23 +25,6 @@ int32_t UserAdminDeleteServlet::handle(
do {
DEFINE_AND_CHECK_TYPE(result, int64_t, user_id, "user_id");

int64_t uid = getUserId(request);
if (!uid) {
result->setErrno(errcode::NOT_LOGIN);
break;
}
auto current_user = UserMgr::GetInstance()->get(uid);
if (!current_user) {
result->setErrno(errcode::USER_NOT_FOUND);
break;
}
int32_t role = current_user->getRole();

if (role != UserManager::Role::ADMIN) {
result->setErrno(errcode::ACCESS_DENIED);
break;
}

auto info = UserMgr::GetInstance()->get(user_id);
if (!info) {
result->setErrno(errcode::USER_NOT_FOUND);
Expand Down
2 changes: 1 addition & 1 deletion src/servlets/user/user_admin_delete_servlet.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace blog {
namespace servlet {

class UserAdminDeleteServlet : public BlogLoginedServlet {
class UserAdminDeleteServlet : public BlogAdminServlet {
public:
typedef std::shared_ptr<UserAdminDeleteServlet> ptr;
UserAdminDeleteServlet();
Expand Down
19 changes: 1 addition & 18 deletions src/servlets/user/user_admin_detail_servlet.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace blog {
namespace servlet {

UserAdminDetailServlet::UserAdminDetailServlet()
: BlogLoginedServlet("UserAdminDetailServlet")
: BlogAdminServlet("UserAdminDetailServlet")
{
}

Expand All @@ -22,23 +22,6 @@ int32_t UserAdminDetailServlet::handle(
do {
DEFINE_AND_CHECK_TYPE(result, int64_t, user_id, "user_id");

int64_t uid = getUserId(request);
if (!uid) {
result->setErrno(errcode::NOT_LOGIN);
break;
}
auto current_user = UserMgr::GetInstance()->get(uid);
if (!current_user) {
result->setErrno(errcode::USER_NOT_FOUND);
break;
}
int32_t role = current_user->getRole();

if (role != UserManager::Role::ADMIN) {
result->setErrno(errcode::ACCESS_DENIED);
break;
}

auto info = UserMgr::GetInstance()->get(user_id);
if (!info) {
result->setErrno(errcode::USER_NOT_FOUND);
Expand Down
2 changes: 1 addition & 1 deletion src/servlets/user/user_admin_detail_servlet.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace blog {
namespace servlet {

class UserAdminDetailServlet : public BlogLoginedServlet {
class UserAdminDetailServlet : public BlogAdminServlet {
public:
typedef std::shared_ptr<UserAdminDetailServlet> ptr;
UserAdminDetailServlet();
Expand Down
19 changes: 1 addition & 18 deletions src/servlets/user/user_admin_lists_servlet.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace servlet {
static chen::Logger::ptr g_logger = LOG_ROOT();

UserAdminListsServlet::UserAdminListsServlet()
: BlogLoginedServlet("UserAdminListsServlet")
: BlogAdminServlet("UserAdminListsServlet")
{
}

Expand All @@ -33,23 +33,6 @@ int32_t UserAdminListsServlet::handle(
int32_t state = request->getParamAs<int32_t>("state", -1);
int32_t days = request->getParamAs<int32_t>("days", 0);

int64_t uid = getUserId(request);
if (!uid) {
result->setErrno(errcode::NOT_LOGIN);
break;
}
auto current_user = UserMgr::GetInstance()->get(uid);
if (!current_user) {
result->setErrno(errcode::USER_NOT_FOUND);
break;
}
int32_t role = current_user->getRole();

if (role != UserManager::Role::ADMIN) {
result->setErrno(errcode::ACCESS_DENIED);
break;
}

uint64_t offset = (page_num - 1) * page_size;
std::vector<data::UserInfo::ptr> users;
uint64_t total = UserMgr::GetInstance()->listByPages(users, offset, page_size, rrole, state, days, true);
Expand Down
2 changes: 1 addition & 1 deletion src/servlets/user/user_admin_lists_servlet.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace blog {
namespace servlet {

class UserAdminListsServlet : public BlogLoginedServlet {
class UserAdminListsServlet : public BlogAdminServlet {
public:
typedef std::shared_ptr<UserAdminListsServlet> ptr;
UserAdminListsServlet();
Expand Down
19 changes: 1 addition & 18 deletions src/servlets/user/user_admin_recover_servlet.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace servlet {
static chen::Logger::ptr logger = LOG_ROOT();

UserAdminRecoverServlet::UserAdminRecoverServlet()
: BlogLoginedServlet("UserAdminRecoverServlet")
: BlogAdminServlet("UserAdminRecoverServlet")
{
}

Expand All @@ -33,23 +33,6 @@ int32_t UserAdminRecoverServlet::handle(
user_ids.insert(chen::TypeUtil::Atoi(i));
}

int64_t uid = getUserId(request);
if (!uid) {
result->setErrno(errcode::NOT_LOGIN);
break;
}
auto current_user = UserMgr::GetInstance()->get(uid);
if (!current_user) {
result->setErrno(errcode::USER_NOT_FOUND);
break;
}
int32_t role = current_user->getRole();

if (role != UserManager::Role::ADMIN) {
result->setErrno(errcode::ACCESS_DENIED);
break;
}

std::vector<data::UserInfo::ptr> infos;
for (const int64_t& id : user_ids) {
auto info = UserMgr::GetInstance()->get(id);
Expand Down
2 changes: 1 addition & 1 deletion src/servlets/user/user_admin_recover_servlet.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace blog {
namespace servlet {

class UserAdminRecoverServlet : public BlogLoginedServlet {
class UserAdminRecoverServlet : public BlogAdminServlet {
public:
typedef std::shared_ptr<UserAdminRecoverServlet> ptr;
UserAdminRecoverServlet();
Expand Down
Loading
Loading