Skip to content

Commit 56b2359

Browse files
committed
feat: add super-admin registration and authentication flow
- Implement register form with password generation and validation - Add authentication status query to dynamically render login or register form - Update backend contract to version 0.2.2 - Add localization support for registration form - Enhance form error handling with more detailed error messages - Add generate-password-ts package for secure password generation
1 parent 3335c57 commit 56b2359

File tree

15 files changed

+340
-21
lines changed

15 files changed

+340
-21
lines changed

package-lock.json

Lines changed: 31 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@remnawave/frontend",
33
"private": false,
44
"type": "module",
5-
"version": "1.3.3",
5+
"version": "1.4.0",
66
"license": "AGPL-3.0-only",
77
"author": "REMNAWAVE <github.com/remnawave>",
88
"homepage": "https://github.com/remnawave",
@@ -45,7 +45,7 @@
4545
"@mantine/nprogress": "^7.17.0",
4646
"@monaco-editor/react": "^4.7.0",
4747
"@paralleldrive/cuid2": "2.2.2",
48-
"@remnawave/backend-contract": "0.1.14",
48+
"@remnawave/backend-contract": "0.2.2",
4949
"@stablelib/base64": "^2.0.1",
5050
"@stablelib/x25519": "^2.0.1",
5151
"@tabler/icons-react": "^3.30.0",
@@ -58,6 +58,7 @@
5858
"crypto-js": "^4.2.0",
5959
"dayjs": "^1.11.13",
6060
"dotenv": "^16.4.5",
61+
"generate-password-ts": "^1.6.5",
6162
"i18next": "^24.2.2",
6263
"i18next-browser-languagedetector": "^8.0.4",
6364
"i18next-http-backend": "^3.0.2",

public/locales/en/remnawave.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,5 +380,21 @@
380380
"user-deletion-description": "Delete all users with selected status. If you will delete users with \"Active\" status, you should restart all nodes to apply changes.",
381381
"select-status": "Delete by status"
382382
}
383+
},
384+
"register-form": {
385+
"feature": {
386+
"username": "Username",
387+
"password": "Password",
388+
"generate": "Generate & Copy",
389+
"confirm-password": "Confirm password",
390+
"sign-up": "Sign up",
391+
"password-copied": "Password copied",
392+
"password-copied-message": "Password copied to clipboard",
393+
"password-copied-error": "Failed to copy password to clipboard",
394+
"error": "Error",
395+
"passwords-do-not-match": "Passwords do not match",
396+
"password-too-short": "Minimum 12 characters",
397+
"register-description": "Create a super-admin account to get started"
398+
}
383399
}
384400
}

public/locales/fa/remnawave.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,5 +380,21 @@
380380
"user-deletion-description": "حذف تمامی کاربران با وضعیت انتخاب شده. اگر کاربران با وضعیت \"Active\" را حذف می‌کنید، باید تمامی گره‌ها را برای اعمال تغییرات مجدداً راه‌اندازی کنید.",
381381
"select-status": "حذف بر اساس وضعیت"
382382
}
383+
},
384+
"register-form": {
385+
"feature": {
386+
"username": "Username",
387+
"password": "Password",
388+
"generate": "Generate & Copy",
389+
"confirm-password": "Confirm password",
390+
"sign-up": "Sign up",
391+
"password-copied": "Password copied",
392+
"password-copied-message": "Password copied to clipboard",
393+
"password-copied-error": "Failed to copy password to clipboard",
394+
"error": "Error",
395+
"passwords-do-not-match": "Passwords do not match",
396+
"password-too-short": "Minimum 12 characters",
397+
"register-description": "Create a super-admin account to get started"
398+
}
383399
}
384400
}

public/locales/ru/remnawave.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,5 +380,21 @@
380380
"select-status": "Удалить по статусу",
381381
"user-deletion-description": "Удалить всех пользователей с выбранным статусом. \nЕсли вы удалите пользователей со статусом «ACTIVE», вы должны перезапустить все ноды, чтобы применить изменения."
382382
}
383+
},
384+
"register-form": {
385+
"feature": {
386+
"confirm-password": "Подтвердите пароль",
387+
"error": "Ошибка",
388+
"generate": "Сгенерировать и скопировать",
389+
"password": "Пароль",
390+
"password-copied": "Пароль скопирован",
391+
"password-copied-error": "Не удалось скопировать пароль в буфер обмена",
392+
"password-copied-message": "Пароль скопирован в буфер обмена",
393+
"password-too-short": "Минимум 12 символов",
394+
"passwords-do-not-match": "Пароли не совпадают",
395+
"register-description": "Создайте аккаунт супер-админа, чтобы начать",
396+
"sign-up": "Зарегистрироваться",
397+
"username": "Имя пользователя"
398+
}
383399
}
384400
}
File renamed without changes.

src/features/auth/login-form.feature.tsx renamed to src/features/auth/login-form/login-form.feature.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@ import { LoginCommand } from '@remnawave/backend-contract'
33
import { useForm, zodResolver } from '@mantine/form'
44
import { PiSignInDuotone } from 'react-icons/pi'
55
import { useTranslation } from 'react-i18next'
6-
import MD5 from 'crypto-js/md5'
76

87
import { handleFormErrors } from '@shared/utils/misc'
98
import { useAuth } from '@shared/hooks/use-auth'
109
import { useLogin } from '@shared/api/hooks'
1110

12-
export const LoginForm = () => {
11+
export const LoginFormFeature = () => {
1312
const { t } = useTranslation()
1413

1514
const { setIsAuthenticated } = useAuth()
@@ -27,7 +26,7 @@ export const LoginForm = () => {
2726
{
2827
variables: {
2928
username: variables.username,
30-
password: MD5(variables.password).toString()
29+
password: variables.password
3130
}
3231
},
3332
{
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './register-form.feature'
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import {
2+
Button,
3+
Container,
4+
Group,
5+
Paper,
6+
PasswordInput,
7+
Text,
8+
TextInput,
9+
Title
10+
} from '@mantine/core'
11+
import { PiShuffleDuotone, PiSignpostDuotone } from 'react-icons/pi'
12+
import { RegisterCommand } from '@remnawave/backend-contract'
13+
import { notifications } from '@mantine/notifications'
14+
import { useForm, zodResolver } from '@mantine/form'
15+
import { generate } from 'generate-password-ts'
16+
import { useTranslation } from 'react-i18next'
17+
import { useClipboard } from '@mantine/hooks'
18+
import { useEffect } from 'react'
19+
20+
import { handleFormErrors } from '@shared/utils/misc'
21+
import { useAuth } from '@shared/hooks/use-auth'
22+
import { useRegister } from '@shared/api/hooks'
23+
24+
export const RegisterFormFeature = () => {
25+
const { t } = useTranslation()
26+
27+
const { setIsAuthenticated } = useAuth()
28+
29+
const { copy, copied, error } = useClipboard()
30+
31+
const form = useForm({
32+
validate: {
33+
...zodResolver(RegisterCommand.RequestSchema),
34+
confirmPassword: (value, values) =>
35+
value !== values.password
36+
? t('register-form.feature.passwords-do-not-match')
37+
: null,
38+
password: (value) =>
39+
value.length < 12 ? t('register-form.feature.password-too-short') : null
40+
},
41+
initialValues: {
42+
username: '',
43+
password: '',
44+
confirmPassword: ''
45+
}
46+
})
47+
48+
const { mutate: register, isPending: isLoading } = useRegister({
49+
mutationFns: {
50+
onSuccess: () => setIsAuthenticated(true),
51+
onError: (error) => {
52+
handleFormErrors(form, error)
53+
}
54+
}
55+
})
56+
57+
const handleGeneratePassword = () => {
58+
const newPassword = generate({
59+
length: 32,
60+
numbers: true,
61+
symbols: false,
62+
uppercase: true,
63+
lowercase: true,
64+
strict: true
65+
})
66+
67+
form.setValues({
68+
...form.values,
69+
password: newPassword,
70+
confirmPassword: newPassword
71+
})
72+
73+
copy(newPassword)
74+
}
75+
76+
useEffect(() => {
77+
if (error) {
78+
notifications.show({
79+
title: t('register-form.feature.error'),
80+
message: t('register-form.feature.password-copied-error')
81+
})
82+
}
83+
84+
if (copied) {
85+
notifications.show({
86+
title: t('register-form.feature.password-copied'),
87+
message: t('register-form.feature.password-copied-message')
88+
})
89+
}
90+
}, [error, copied])
91+
92+
const handleSubmit = form.onSubmit((variables) => {
93+
register({
94+
variables: {
95+
username: variables.username,
96+
password: variables.password
97+
}
98+
})
99+
})
100+
101+
return (
102+
<form onSubmit={handleSubmit}>
103+
<Container my={40} size={'100%'}>
104+
<Paper mt={30} p={30} radius="md">
105+
<Title mb="xs" order={2} ta="center">
106+
{t('register-form.feature.sign-up')}
107+
</Title>
108+
<Text c="dimmed" mb="md" size="sm" ta="center">
109+
{t('register-form.feature.register-description')}
110+
</Text>
111+
112+
<TextInput
113+
label={t('register-form.feature.username')}
114+
placeholder="IamSuperAdmin"
115+
required
116+
size="md"
117+
{...form.getInputProps('username')}
118+
/>
119+
120+
<Group align="flex-end" mt="md">
121+
<PasswordInput
122+
label={t('register-form.feature.password')}
123+
placeholder="soy_t5Px5`Gm4j0@Hf&Dd7iU"
124+
required
125+
size="md"
126+
style={{ flex: 1 }}
127+
{...form.getInputProps('password')}
128+
/>
129+
</Group>
130+
131+
<PasswordInput
132+
label={t('register-form.feature.confirm-password')}
133+
mt="md"
134+
placeholder="soy_t5Px5`Gm4j0@Hf&Dd7iU"
135+
required
136+
size="md"
137+
{...form.getInputProps('confirmPassword')}
138+
/>
139+
140+
<Button
141+
fullWidth
142+
leftSection={<PiShuffleDuotone size="1rem" />}
143+
mt="xl"
144+
onClick={handleGeneratePassword}
145+
size="md"
146+
variant="light"
147+
>
148+
{t('register-form.feature.generate')}
149+
</Button>
150+
151+
<Button
152+
fullWidth
153+
leftSection={<PiSignpostDuotone size="1rem" />}
154+
loading={isLoading}
155+
mt="xl"
156+
size="md"
157+
type="submit"
158+
>
159+
{t('register-form.feature.sign-up')}
160+
</Button>
161+
</Paper>
162+
</Container>
163+
</form>
164+
)
165+
}

0 commit comments

Comments
 (0)