diff --git a/auth/.env.example b/auth/.env.example index 581d1bad..73452a74 100644 --- a/auth/.env.example +++ b/auth/.env.example @@ -6,6 +6,9 @@ RSA_PRIVATE_KEY_PATH="" PROFILE_PICTURE_PATH="/profile_pictures" +# If true, POST /users registration is disabled and returns 403. +DISABLE_REGISTRATION=false + # json object with the claims to add to every jwt (this is read when creating a new user) EXTRA_CLAIMS='{}' # json object with the claims to add to every jwt of the FIRST user (this can be used to mark the first user as admin). diff --git a/auth/config.go b/auth/config.go index 473bdb4d..e610dea8 100644 --- a/auth/config.go +++ b/auth/config.go @@ -14,6 +14,7 @@ import ( "maps" "os" "slices" + "strconv" "strings" "time" @@ -24,18 +25,19 @@ import ( ) type Configuration struct { - JwtPrivateKey *rsa.PrivateKey - JwtPublicKey *rsa.PublicKey - JwtKid string - PublicUrl string - OidcProviders map[string]OidcProviderConfig - DefaultClaims jwt.MapClaims - FirstUserClaims jwt.MapClaims - GuestClaims jwt.MapClaims - ProtectedClaims []string - ExpirationDelay time.Duration - EnvApiKeys []ApiKeyWToken - ProfilePicturePath string + JwtPrivateKey *rsa.PrivateKey + JwtPublicKey *rsa.PublicKey + JwtKid string + PublicUrl string + OidcProviders map[string]OidcProviderConfig + DefaultClaims jwt.MapClaims + FirstUserClaims jwt.MapClaims + GuestClaims jwt.MapClaims + ProtectedClaims []string + ExpirationDelay time.Duration + EnvApiKeys []ApiKeyWToken + ProfilePicturePath string + DisableRegistration bool } type OidcAuthMethod string @@ -76,6 +78,12 @@ func LoadConfiguration(ctx context.Context, db *dbc.Queries) (*Configuration, er "/profile_pictures", ) + disableRegistration, err := strconv.ParseBool(cmp.Or(os.Getenv("DISABLE_REGISTRATION"), "false")) + if err != nil { + return nil, fmt.Errorf("invalid DISABLE_REGISTRATION value: %w", err) + } + ret.DisableRegistration = disableRegistration + claims := os.Getenv("EXTRA_CLAIMS") if claims != "" { err := json.Unmarshal([]byte(claims), &ret.DefaultClaims) diff --git a/auth/oidc.go b/auth/oidc.go index c22a3c4e..619cb2b9 100644 --- a/auth/oidc.go +++ b/auth/oidc.go @@ -530,8 +530,9 @@ func (h *Handler) OidcUnlink(c *echo.Context) error { } type ServerInfo struct { - PublicUrl string `json:"publicUrl"` - Oidc map[string]OidcInfo `json:"oidc"` + PublicUrl string `json:"publicUrl"` + AllowRegister bool `json:"allowRegister"` + Oidc map[string]OidcInfo `json:"oidc"` } type OidcInfo struct { @@ -547,8 +548,9 @@ type OidcInfo struct { // @Router /info [get] func (h *Handler) Info(c *echo.Context) error { ret := ServerInfo{ - PublicUrl: h.config.PublicUrl, - Oidc: make(map[string]OidcInfo), + PublicUrl: h.config.PublicUrl, + AllowRegister: !h.config.DisableRegistration, + Oidc: make(map[string]OidcInfo), } for _, provider := range h.config.OidcProviders { ret.Oidc[provider.Id] = OidcInfo{ diff --git a/auth/users.go b/auth/users.go index b59f2457..194dd38f 100644 --- a/auth/users.go +++ b/auth/users.go @@ -159,9 +159,14 @@ func (h *Handler) GetMe(c *echo.Context) error { // @Param user body RegisterDto false "Registration informations" // @Success 201 {object} SessionWToken // @Success 409 {object} KError "Duplicated email or username" +// @Failure 403 {object} KError "Registrations are disabled" // @Failure 422 {object} KError "Invalid register body" // @Router /users [post] func (h *Handler) Register(c *echo.Context) error { + if h.config.DisableRegistration { + return echo.NewHTTPError(http.StatusForbidden, "Registrations are disabled") + } + ctx := c.Request().Context() var req RegisterDto err := c.Bind(&req) diff --git a/front/public/translations/en.json b/front/public/translations/en.json index 6b9c40ba..dd344ee4 100644 --- a/front/public/translations/en.json +++ b/front/public/translations/en.json @@ -250,7 +250,9 @@ "or-login": "Have an account already? <1>Log in.", "password-no-match": "Passwords do not match.", "delete": "Delete your account", - "delete-confirmation": "This action can't be reverted. Are you sure?" + "delete-confirmation": "This action can't be reverted. Are you sure?", + "register-disabled": "Registrations are disabled.", + "register-disabled-oidc": "Password registration is disabled. Use OIDC." }, "downloads": { "empty": "Nothing downloaded yet, start browsing for something you like", diff --git a/front/src/ui/login/login.tsx b/front/src/ui/login/login.tsx index 8dfcbfd3..a6ef3bf4 100644 --- a/front/src/ui/login/login.tsx +++ b/front/src/ui/login/login.tsx @@ -4,6 +4,7 @@ import { Trans, useTranslation } from "react-i18next"; import { Platform } from "react-native"; import { A, Button, H1, Input, P } from "~/primitives"; import { defaultApiUrl } from "~/providers/account-provider"; +import { useFetch } from "~/query"; import { useQueryState } from "~/utils"; import { FormPage } from "./form"; import { login } from "./logic"; @@ -20,46 +21,48 @@ export const LoginPage = () => { const { t } = useTranslation(); const router = useRouter(); + const { data: info } = useFetch(OidcLogin.query(apiUrl)); if (Platform.OS !== "web" && !apiUrl) return ; return (

{t("login.login")}

- -

{t("login.username")}

- setUsername(value)} - autoCapitalize="none" - /> -

{t("login.password")}

- setPassword(value)} - /> - {error &&

{error}

} -