mirror of
https://github.com/rommapp/romm.git
synced 2026-06-28 06:46:00 +00:00
Make invite token expiration configurable via env var and UI
Co-authored-by: gantoine <3247106+gantoine@users.noreply.github.com>
This commit is contained in:
@@ -126,6 +126,9 @@ DISABLE_USERPASS_LOGIN: Final[bool] = safe_str_to_bool(
|
||||
_get_env("DISABLE_USERPASS_LOGIN")
|
||||
)
|
||||
DISABLE_SETUP_WIZARD: Final[bool] = safe_str_to_bool(_get_env("DISABLE_SETUP_WIZARD"))
|
||||
INVITE_TOKEN_EXPIRY_MINUTES: Final[int] = safe_int(
|
||||
_get_env("INVITE_TOKEN_EXPIRY_MINUTES"), 10
|
||||
)
|
||||
|
||||
# OIDC
|
||||
OIDC_ENABLED: Final[bool] = safe_str_to_bool(_get_env("OIDC_ENABLED"))
|
||||
|
||||
@@ -108,12 +108,16 @@ def add_user(
|
||||
[],
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
def create_invite_link(request: Request, role: str) -> InviteLinkSchema:
|
||||
def create_invite_link(
|
||||
request: Request, role: str, expiration_minutes: int | None = None
|
||||
) -> InviteLinkSchema:
|
||||
"""Create an invite link for a user.
|
||||
|
||||
Args:
|
||||
request (Request): FastAPI Request object
|
||||
role (str): The role of the user
|
||||
expiration_minutes (int | None): Token expiration in minutes. Defaults to
|
||||
the INVITE_TOKEN_EXPIRY_MINUTES environment variable.
|
||||
|
||||
Returns:
|
||||
InviteLinkSchema: Invite link
|
||||
@@ -136,7 +140,17 @@ def create_invite_link(request: Request, role: str) -> InviteLinkSchema:
|
||||
detail=msg,
|
||||
)
|
||||
|
||||
token = auth_handler.generate_invite_link_token(request.user, role=role)
|
||||
if expiration_minutes is not None and expiration_minutes <= 0:
|
||||
msg = "expiration_minutes must be a positive integer"
|
||||
log.error(msg)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=msg,
|
||||
)
|
||||
|
||||
token = auth_handler.generate_invite_link_token(
|
||||
request.user, role=role, expiration_minutes=expiration_minutes
|
||||
)
|
||||
return InviteLinkSchema.model_validate({"token": token})
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ from passlib.context import CryptContext
|
||||
from starlette.requests import HTTPConnection
|
||||
|
||||
from config import (
|
||||
INVITE_TOKEN_EXPIRY_MINUTES,
|
||||
OIDC_CLAIM_ROLES,
|
||||
OIDC_ENABLED,
|
||||
OIDC_ROLE_ADMIN,
|
||||
@@ -35,7 +36,6 @@ class AuthHandler:
|
||||
def __init__(self) -> None:
|
||||
self.pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
self.reset_passwd_token_expires_in_minutes = 10
|
||||
self.invite_link_token_expires_in_minutes = 10
|
||||
|
||||
def verify_password(self, plain_password, hashed_password):
|
||||
return self.pwd_context.verify(plain_password, hashed_password)
|
||||
@@ -169,15 +169,20 @@ class AuthHandler:
|
||||
)
|
||||
await RedisSessionMiddleware.clear_user_sessions(user.username)
|
||||
|
||||
def generate_invite_link_token(self, user: Any, role: str) -> str:
|
||||
def generate_invite_link_token(
|
||||
self, user: Any, role: str, expiration_minutes: int | None = None
|
||||
) -> str:
|
||||
"""
|
||||
Generate an invite link token for the user.
|
||||
Args:
|
||||
user (Any): The user object.
|
||||
role (str): The role of the user.
|
||||
expiration_minutes (int | None): Token expiration in minutes. Defaults to
|
||||
the INVITE_TOKEN_EXPIRY_MINUTES environment variable.
|
||||
Returns:
|
||||
str: The generated invite link token.
|
||||
"""
|
||||
expires_in = expiration_minutes if expiration_minutes is not None else INVITE_TOKEN_EXPIRY_MINUTES
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
jti = str(uuid.uuid4())
|
||||
@@ -189,7 +194,7 @@ class AuthHandler:
|
||||
"iat": int(now.timestamp()),
|
||||
"exp": int(
|
||||
(
|
||||
now + timedelta(minutes=self.invite_link_token_expires_in_minutes)
|
||||
now + timedelta(minutes=expires_in)
|
||||
).timestamp()
|
||||
),
|
||||
"jti": jti,
|
||||
@@ -204,7 +209,7 @@ class AuthHandler:
|
||||
f"Invite link created by {hl(user.username, color=CYAN)}: {hl(invite_link)}"
|
||||
)
|
||||
redis_client.setex(
|
||||
f"invite-jti:{jti}", self.invite_link_token_expires_in_minutes * 60, "valid"
|
||||
f"invite-jti:{jti}", expires_in * 60, "valid"
|
||||
)
|
||||
return token
|
||||
|
||||
|
||||
@@ -11,7 +11,17 @@ const { lgAndUp } = useDisplay();
|
||||
const show = ref(false);
|
||||
const fullInviteLink = ref("");
|
||||
const selectedRole = ref("");
|
||||
const selectedExpiration = ref<number>(1440);
|
||||
const roles = ["viewer", "editor", "admin"];
|
||||
const expirationOptions = [
|
||||
{ label: "1 hour", value: 60 },
|
||||
{ label: "6 hours", value: 360 },
|
||||
{ label: "12 hours", value: 720 },
|
||||
{ label: "1 day", value: 1440 },
|
||||
{ label: "3 days", value: 4320 },
|
||||
{ label: "7 days", value: 10080 },
|
||||
{ label: "30 days", value: 43200 },
|
||||
];
|
||||
const emitter = inject<Emitter<Events>>("emitter");
|
||||
emitter?.on("showCreateInviteLinkDialog", () => {
|
||||
show.value = true;
|
||||
@@ -19,7 +29,10 @@ emitter?.on("showCreateInviteLinkDialog", () => {
|
||||
|
||||
function createInviteLink() {
|
||||
userApi
|
||||
.createInviteLink({ role: selectedRole.value })
|
||||
.createInviteLink({
|
||||
role: selectedRole.value,
|
||||
expirationMinutes: selectedExpiration.value,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
emitter?.emit("snackbarShow", {
|
||||
msg: "Invite link created",
|
||||
@@ -65,6 +78,20 @@ function closeDialog() {
|
||||
>{{ role.charAt(0).toUpperCase() + role.slice(1) }}
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-row>
|
||||
<v-row class="justify-center pa-2" no-gutters>
|
||||
<v-select
|
||||
v-model="selectedExpiration"
|
||||
:items="expirationOptions"
|
||||
item-title="label"
|
||||
item-value="value"
|
||||
label="Expires in"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
class="ma-1"
|
||||
style="max-width: 200px"
|
||||
/>
|
||||
<v-btn-toggle class="text-primary ma-1" divided>
|
||||
<v-btn
|
||||
:disabled="!selectedRole"
|
||||
|
||||
@@ -24,12 +24,18 @@ async function createUser({
|
||||
return api.post<UserSchema>("/users", payload);
|
||||
}
|
||||
|
||||
async function createInviteLink({ role }: { role: string }) {
|
||||
return api.post<InviteLinkSchema>(
|
||||
"/users/invite-link",
|
||||
{},
|
||||
{ params: { role } },
|
||||
);
|
||||
async function createInviteLink({
|
||||
role,
|
||||
expirationMinutes,
|
||||
}: {
|
||||
role: string;
|
||||
expirationMinutes?: number;
|
||||
}) {
|
||||
const params: Record<string, string | number> = { role };
|
||||
if (expirationMinutes !== undefined) {
|
||||
params.expiration_minutes = expirationMinutes;
|
||||
}
|
||||
return api.post<InviteLinkSchema>("/users/invite-link", {}, { params });
|
||||
}
|
||||
|
||||
async function registerUser(
|
||||
|
||||
Reference in New Issue
Block a user