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
8 changes: 7 additions & 1 deletion client/src/vue_components/user/CreateUser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,16 @@ export default {
event.preventDefault();
} else {
await this.CREATE_USER(this.state);
if (this.isFirstAdmin) {
await this.USER_LOGIN({
username: this.state.username,
password: this.state.password,
});
}
this.$emit('created_user');
}
},
...mapActions(['CREATE_USER']),
...mapActions(['CREATE_USER', 'USER_LOGIN']),
},
};
</script>
86 changes: 47 additions & 39 deletions server/controllers/api/auth/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,62 +15,71 @@
api_authenticated,
no_live_session,
require_admin,
requires_show,
)


@ApiRoute("auth/create", ApiVersion.V1)
class UserCreateController(BaseAPIController):
async def post(self):
data = escape.json_decode(self.request.body)
with self.make_session() as session:
# If there are no users, allow creation without authentication, otherwise require admin.
has_any_users = session.scalars(select(User)).first() is not None
if has_any_users:
self.requires_admin()

username = data.get("username", "")
if not username:
self.set_status(400)
await self.finish({"message": "Username missing"})
return
data = escape.json_decode(self.request.body)

password = data.get("password", "")
if not password:
self.set_status(400)
await self.finish({"message": "Password missing"})
return
username = data.get("username", "")
if not username:
self.set_status(400)
await self.finish({"message": "Username missing"})
return

# Validate password strength
is_valid, error_msg = PasswordService.validate_password_strength(password)
if not is_valid:
self.set_status(400)
await self.finish({"message": error_msg})
return
password = data.get("password", "")
if not password:
self.set_status(400)
await self.finish({"message": "Password missing"})
return

is_admin = data.get("is_admin", False)
is_admin = data.get("is_admin", False)
if not has_any_users and not is_admin:
self.set_status(400)
await self.finish({"message": "First user must be an admin"})
return

with self.make_session() as session:
conflict_user = session.scalars(
select(User).where(User.username == username)
).first()
if conflict_user:
# Validate password strength
is_valid, error_msg = PasswordService.validate_password_strength(password)
if not is_valid:
self.set_status(400)
await self.finish({"message": "Username already taken"})
await self.finish({"message": error_msg})
return

hashed_password = await PasswordService.hash_password(password)
async with NamedLockRegistry.acquire(f"UserLock::{username}"):
conflict_user = session.scalars(
select(User).where(User.username == username)
).first()
if conflict_user:
self.set_status(400)
await self.finish({"message": "Username already taken"})
return

session.add(
User(
username=username,
password=hashed_password,
is_admin=is_admin,
hashed_password = await PasswordService.hash_password(password)

session.add(
User(
username=username,
password=hashed_password,
is_admin=is_admin,
)
)
)
session.commit()
session.commit()

if is_admin:
await self.application.digi_settings.set("has_admin_user", True)
if is_admin:
await self.application.digi_settings.set("has_admin_user", True)

self.set_status(200)
await self.application.ws_send_to_all("NOOP", "GET_USERS", {})
await self.finish({"message": "Successfully created user"})
self.set_status(200)
await self.application.ws_send_to_all("NOOP", "GET_USERS", {})
await self.finish({"message": "Successfully created user"})


@ApiRoute("auth/delete", ApiVersion.V1)
Expand Down Expand Up @@ -233,7 +242,6 @@ async def post(self):
class UsersHandler(BaseAPIController):
@api_authenticated
@require_admin
@requires_show
def get(self):
user_schema = UserSchema()
with self.make_session() as session:
Expand Down
Loading
Loading