diff --git a/api/user/user.go b/api/user/user.go index d36d8c3e..e695ac44 100644 --- a/api/user/user.go +++ b/api/user/user.go @@ -3,6 +3,7 @@ package user import ( "github.com/0xJacky/Nginx-UI/internal/user" "github.com/0xJacky/Nginx-UI/model" + "github.com/0xJacky/Nginx-UI/query" "github.com/0xJacky/Nginx-UI/settings" "github.com/gin-gonic/gin" "github.com/uozi-tech/cosy" @@ -40,6 +41,12 @@ func InitManageUserRouter(g *gin.RouterGroup) { } }) c.BeforeDecodeHook(encryptPassword) + c.ExecutedHook(func(ctx *cosy.Ctx[model.User]) { + if ctx.Payload["password"] != "" { + a := query.AuthToken + _, _ = a.Where(a.UserID.Eq(ctx.ID)).Delete() + } + }) }) c.DestroyHook(func(c *cosy.Ctx[model.User]) { diff --git a/app/src/components/OTPInput/OTPInput.vue b/app/src/components/OTPInput/OTPInput.vue index 6841b052..7f6ffd03 100644 --- a/app/src/components/OTPInput/OTPInput.vue +++ b/app/src/components/OTPInput/OTPInput.vue @@ -3,7 +3,9 @@ import VOtpInput from 'vue3-otp-input' const emit = defineEmits(['onComplete']) -const data = defineModel() +const data = defineModel({ + default: '', +}) // eslint-disable-next-line vue/require-typed-ref const refOtp = ref() diff --git a/app/src/views/other/Login.vue b/app/src/views/other/Login.vue index 9295e2bd..6ff824be 100644 --- a/app/src/views/other/Login.vue +++ b/app/src/views/other/Login.vue @@ -190,7 +190,6 @@ async function handlePasskeyLogin() { = { collapsed: false, items: [ { text: 'Nginx Proxy Example', link: '/guide/nginx-proxy-example' }, + { text: 'Reset Password', link: '/guide/reset-password' }, { text: 'License', link: '/guide/license' } ] } diff --git a/docs/.vitepress/config/zh_CN.ts b/docs/.vitepress/config/zh_CN.ts index 433853c9..c33de441 100644 --- a/docs/.vitepress/config/zh_CN.ts +++ b/docs/.vitepress/config/zh_CN.ts @@ -69,6 +69,7 @@ export const zhCNConfig: LocaleSpecificConfig = { collapsed: false, items: [ { text: 'Nginx 代理示例', link: '/zh_CN/guide/nginx-proxy-example' }, + { text: '重置密码', link: '/zh_CN/guide/reset-password' }, { text: '开源协议', link: '/zh_CN/guide/license' } ] } diff --git a/docs/.vitepress/config/zh_TW.ts b/docs/.vitepress/config/zh_TW.ts index a4830bc2..5deba027 100644 --- a/docs/.vitepress/config/zh_TW.ts +++ b/docs/.vitepress/config/zh_TW.ts @@ -69,6 +69,7 @@ export const zhTWConfig: LocaleSpecificConfig = { collapsed: false, items: [ { text: 'Nginx 代理示例', link: '/zh_TW/guide/nginx-proxy-example' }, + { text: '重置密碼', link: '/zh_TW/guide/reset-password' }, { text: '開源協議', link: '/zh_TW/guide/license' } ] } diff --git a/docs/guide/reset-password.md b/docs/guide/reset-password.md new file mode 100644 index 00000000..0a2d16ef --- /dev/null +++ b/docs/guide/reset-password.md @@ -0,0 +1,61 @@ +# Reset Initial User Password + +The `reset-password` command allows you to reset the initial administrator account's password to a randomly generated 12-character password that includes uppercase letters, lowercase letters, numbers, and special symbols. + +## Usage + +To reset the initial user's password, run: + +```bash +nginx-ui reset-password --config=/path/to/app.ini +``` + +The command will: +1. Generate a secure random password (12 characters) +2. Reset the password for the initial user account (user ID 1) +3. Output the new password in the application logs + +## Parameters + +- `--config`: (Required) Path to the Nginx UI configuration file + +## Example + +```bash +# Reset the password using the default config file location +nginx-ui reset-password --config=/path/to/app.ini + +# The output will include the generated password +2025-03-03 03:24:41 INFO user/reset_password.go:52 confPath: ../app.ini +2025-03-03 03:24:41 INFO user/reset_password.go:59 dbPath: ../database.db +2025-03-03 03:24:41 INFO user/reset_password.go:92 User: root, Password: X&K^(X0m(E&& +``` + +## Configuration File Location + +- If you installed Nginx UI using the Linux one-click installation script, the configuration file is located at: + ``` + /usr/local/etc/nginx-ui/app.ini + ``` + + You can directly use the following command: + ```bash + nginx-ui reset-password --config /usr/local/etc/nginx-ui/app.ini + ``` + +## Docker Usage + +If you're running Nginx UI in a Docker container, you need to use the `docker exec` command: + +```bash +docker exec -it nginx-ui reset-password --config=/etc/nginx-ui/app.ini +``` + +Replace `` with your actual container name or ID. + +## Notes + +- This command is useful if you've forgotten the initial administrator password +- The new password will be displayed in the logs, so be sure to copy it immediately +- You must have access to the server's command line to use this feature +- The database file must exist for this command to work \ No newline at end of file diff --git a/docs/zh_CN/guide/reset-password.md b/docs/zh_CN/guide/reset-password.md new file mode 100644 index 00000000..8c8433a0 --- /dev/null +++ b/docs/zh_CN/guide/reset-password.md @@ -0,0 +1,61 @@ +# 重置初始用户密码 + +`reset-password` 命令允许您将初始管理员账户的密码重置为随机生成的12位密码,包含大写字母、小写字母、数字和特殊符号。 + +## 使用方法 + +要重置初始用户的密码,请运行: + +```bash +nginx-ui reset-password --config=/path/to/app.ini +``` + +此命令将: +1. 生成一个安全的随机密码(12个字符) +2. 重置初始用户账户(用户ID 1)的密码 +3. 在应用程序日志中输出新密码 + +## 参数 + +- `--config`:(必填)Nginx UI 配置文件的路径 + +## 示例 + +```bash +# 使用默认配置文件位置重置密码 +nginx-ui reset-password --config=/path/to/app.ini + +# 输出将包含生成的密码 +2025-03-03 03:24:41 INFO user/reset_password.go:52 confPath: ../app.ini +2025-03-03 03:24:41 INFO user/reset_password.go:59 dbPath: ../database.db +2025-03-03 03:24:41 INFO user/reset_password.go:92 User: root, Password: X&K^(X0m(E&& +``` + +## 配置文件位置 + +- 如果您使用 Linux 一键安装脚本安装的 Nginx UI,配置文件位于: + ``` + /usr/local/etc/nginx-ui/app.ini + ``` + + 您可以直接使用以下命令: + ```bash + nginx-ui reset-password --config /usr/local/etc/nginx-ui/app.ini + ``` + +## Docker 使用方法 + +如果您在 Docker 容器中运行 Nginx UI,需要使用 `docker exec` 命令: + +```bash +docker exec -it nginx-ui reset-password --config=/etc/nginx-ui/app.ini +``` + +请将 `` 替换为您实际的容器名称或 ID。 + +## 注意事项 + +- 如果您忘记了初始管理员密码,此命令很有用 +- 新密码将显示在日志中,请确保立即复制它 +- 您必须有权访问服务器的命令行才能使用此功能 +- 数据库文件必须存在才能使此命令正常工作 \ No newline at end of file diff --git a/docs/zh_TW/guide/reset-password.md b/docs/zh_TW/guide/reset-password.md new file mode 100644 index 00000000..aa8b8820 --- /dev/null +++ b/docs/zh_TW/guide/reset-password.md @@ -0,0 +1,61 @@ +# 重置初始用戶密碼 + +`reset-password` 命令允許您將初始管理員賬戶的密碼重置為隨機生成的12位密碼,包含大寫字母、小寫字母、數字和特殊符號。 + +## 使用方法 + +要重置初始用戶的密碼,請運行: + +```bash +nginx-ui reset-password --config=/path/to/app.ini +``` + +此命令將: +1. 生成一個安全的隨機密碼(12個字符) +2. 重置初始用戶賬戶(用戶ID 1)的密碼 +3. 在應用程序日誌中輸出新密碼 + +## 參數 + +- `--config`:(必填)Nginx UI 配置文件的路徑 + +## 示例 + +```bash +# 使用默認配置文件位置重置密碼 +nginx-ui reset-password --config=/path/to/app.ini + +# 輸出將包含生成的密碼 +2025-03-03 03:24:41 INFO user/reset_password.go:52 confPath: ../app.ini +2025-03-03 03:24:41 INFO user/reset_password.go:59 dbPath: ../database.db +2025-03-03 03:24:41 INFO user/reset_password.go:92 User: root, Password: X&K^(X0m(E&& +``` + +## 配置文件位置 + +- 如果您使用 Linux 一鍵安裝腳本安裝的 Nginx UI,配置文件位於: + ``` + /usr/local/etc/nginx-ui/app.ini + ``` + + 您可以直接使用以下命令: + ```bash + nginx-ui reset-password --config /usr/local/etc/nginx-ui/app.ini + ``` + +## Docker 使用方法 + +如果您在 Docker 容器中運行 Nginx UI,需要使用 `docker exec` 命令: + +```bash +docker exec -it nginx-ui reset-password --config=/etc/nginx-ui/app.ini +``` + +請將 `` 替換為您實際的容器名稱或 ID。 + +## 注意事項 + +- 如果您忘記了初始管理員密碼,此命令很有用 +- 新密碼將顯示在日誌中,請確保立即複製它 +- 您必須有權訪問服務器的命令行才能使用此功能 +- 數據庫文件必須存在才能使此命令正常工作 \ No newline at end of file diff --git a/internal/cmd/main.go b/internal/cmd/main.go index c84ae9fc..5bbb1fad 100644 --- a/internal/cmd/main.go +++ b/internal/cmd/main.go @@ -5,6 +5,7 @@ import ( "log" "os" + "github.com/0xJacky/Nginx-UI/internal/user" "github.com/0xJacky/Nginx-UI/internal/version" "github.com/urfave/cli/v3" ) @@ -24,6 +25,11 @@ func NewAppCmd() *cli.Command { return nil }, }, + { + Name: "reset-password", + Usage: "Reset the initial user password", + Action: user.ResetInitUserPassword, + }, }, Flags: []cli.Flag{ &cli.StringFlag{ diff --git a/internal/user/reset_password.go b/internal/user/reset_password.go new file mode 100644 index 00000000..5b4a974d --- /dev/null +++ b/internal/user/reset_password.go @@ -0,0 +1,97 @@ +package user + +import ( + "context" + "crypto/rand" + "math/big" + "os" + "path" + + "github.com/0xJacky/Nginx-UI/model" + "github.com/0xJacky/Nginx-UI/query" + "github.com/0xJacky/Nginx-UI/settings" + "github.com/gin-gonic/gin" + "github.com/pkg/errors" + "github.com/uozi-tech/cosy" + sqlite "github.com/uozi-tech/cosy-driver-sqlite" + "github.com/uozi-tech/cosy/logger" + cSettings "github.com/uozi-tech/cosy/settings" + "github.com/urfave/cli/v3" + "golang.org/x/crypto/bcrypt" +) + +var ( + ErrConfigNotFound = errors.New("config not found") + ErrDBFileNotFound = errors.New("db file not found") + ErrInitUserNotExists = errors.New("init user not exists") +) + +func generateRandomPassword(length int) (string, error) { + const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+" + password := make([]byte, length) + charsetLength := big.NewInt(int64(len(charset))) + + for i := 0; i < length; i++ { + randomIndex, err := rand.Int(rand.Reader, charsetLength) + if err != nil { + return "", err + } + password[i] = charset[randomIndex.Int64()] + } + return string(password), nil +} + +func ResetInitUserPassword(ctx context.Context, command *cli.Command) error { + confPath := command.String("config") + settings.Init(confPath) + + cSettings.ServerSettings.RunMode = gin.ReleaseMode + + logger.Init(cSettings.ServerSettings.RunMode) + + logger.Infof("confPath: %s", confPath) + + if _, err := os.Stat(confPath); os.IsNotExist(err) { + return ErrConfigNotFound + } + + dbPath := path.Join(path.Dir(confPath), settings.DatabaseSettings.Name+".db") + logger.Infof("dbPath: %s", dbPath) + // check if db file exists + if _, err := os.Stat(dbPath); os.IsNotExist(err) { + return ErrDBFileNotFound + } + + db := cosy.InitDB(sqlite.Open(path.Dir(cSettings.ConfPath), settings.DatabaseSettings)) + model.Use(db) + query.Init(db) + + u := query.User + user, err := u.FirstByID(1) + if err != nil { + return ErrInitUserNotExists + } + + pwd, err := generateRandomPassword(12) + if err != nil { + return err + } + + pwdBytes, err := bcrypt.GenerateFromPassword([]byte(pwd), bcrypt.DefaultCost) + if err != nil { + return err + } + + _, err = u.Where(u.ID.Eq(1)).Updates(&model.User{ + Password: string(pwdBytes), + }) + if err != nil { + return err + } + + a := query.AuthToken + _, _ = a.Where(a.UserID.Eq(1)).Delete() + + logger.Infof("User: %s, Password: %s", user.Name, pwd) + return nil +}