diff --git a/.github/workflows/auth-hurl.yml b/.github/workflows/auth-hurl.yml
index d4db2c7f..619fdd2b 100644
--- a/.github/workflows/auth-hurl.yml
+++ b/.github/workflows/auth-hurl.yml
@@ -31,7 +31,7 @@ jobs:
- uses: actions/setup-go@v6
with:
- go-version: '^1.22.5'
+ go-version: '^1.26.0'
cache-dependency-path: ./auth/go.sum
- name: Install dependencies
diff --git a/auth/models/users.go b/auth/models/users.go
index 7e6c04ca..de704348 100644
--- a/auth/models/users.go
+++ b/auth/models/users.go
@@ -16,6 +16,8 @@ type User struct {
Username string `json:"username" example:"zoriya"`
// Email of the user. Can be used as a login.
Email string `json:"email" format:"email" example:"kyoo@zoriya.dev"`
+ // False if the user has never setup a password and only used oidc.
+ HasPassword bool `json:"hasPassword"`
// When was this account created?
CreatedDate time.Time `json:"createdDate" example:"2025-03-29T18:20:05.267Z"`
// When was the last time this account made any authorized request?
@@ -52,7 +54,6 @@ type EditUserDto struct {
}
type EditPasswordDto struct {
- OldPassword string `json:"oldPassword" validate:"required" example:"password1234"`
- NewPassword string `json:"newPassword" validate:"required" example:"password1234"`
+ OldPassword *string `json:"oldPassword" example:"password1234"`
+ NewPassword string `json:"newPassword" validate:"required" example:"password1234"`
}
-
diff --git a/auth/oidc.go b/auth/oidc.go
index bd8420ae..a6b6aa17 100644
--- a/auth/oidc.go
+++ b/auth/oidc.go
@@ -468,14 +468,14 @@ func (h *Handler) OidcUnlink(c *echo.Context) error {
ctx := c.Request().Context()
user, err := h.db.GetUser(ctx, dbc.GetUserParams{UseId: true, Id: uid})
- if err != nil {
- return err
- }
if err == pgx.ErrNoRows {
return echo.NewHTTPError(http.StatusNotFound, "No user found")
} else if err != nil {
return nil
}
+ if user.User.Password == nil {
+ return echo.NewHTTPError(http.StatusUnprocessableEntity, "You must configure a password before unlinking your OIDC provider")
+ }
err = h.db.DeleteOidcHandle(ctx, dbc.DeleteOidcHandleParams{
UserPk: user.User.Pk,
diff --git a/auth/users.go b/auth/users.go
index 461600fb..5a0ff5f2 100644
--- a/auth/users.go
+++ b/auth/users.go
@@ -23,6 +23,7 @@ func MapDbUser(user *dbc.User) User {
Id: user.Id,
Username: user.Username,
Email: user.Email,
+ HasPassword: user.Password != nil,
CreatedDate: user.CreatedDate,
LastSeen: user.LastSeen,
Claims: user.Claims,
@@ -474,15 +475,20 @@ func (h *Handler) ChangePassword(c *echo.Context) error {
return err
}
- match, err := argon2id.ComparePasswordAndHash(
- req.OldPassword,
- *user.User.Password,
- )
- if err != nil {
- return err
- }
- if !match {
- return echo.NewHTTPError(http.StatusForbidden, "Invalid password")
+ if user.User.Password != nil {
+ if req.OldPassword == nil {
+ return echo.NewHTTPError(http.StatusUnprocessableEntity, "Missing old password")
+ }
+ match, err := argon2id.ComparePasswordAndHash(
+ *req.OldPassword,
+ *user.User.Password,
+ )
+ if err != nil {
+ return err
+ }
+ if !match {
+ return echo.NewHTTPError(http.StatusForbidden, "Invalid password")
+ }
}
pass, err := argon2id.CreateHash(req.NewPassword, argon2id.DefaultParams)
diff --git a/front/src/models/user.ts b/front/src/models/user.ts
index 22f90b7e..495f127d 100644
--- a/front/src/models/user.ts
+++ b/front/src/models/user.ts
@@ -5,9 +5,9 @@ export const User = z
id: z.string(),
username: z.string(),
email: z.string(),
+ hasPassword: z.boolean().default(true),
claims: z.object({
permissions: z.array(z.string()),
- // hasPassword: z.boolean().default(true),
settings: z
.object({
downloadQuality: z
@@ -37,7 +37,7 @@ export const User = z
z.string(),
z.object({
id: z.string(),
- username: z.string().nullable().default(""),
+ username: z.string(),
profileUrl: z.string().nullable(),
}),
)
diff --git a/front/src/primitives/button.tsx b/front/src/primitives/button.tsx
index 6d1df9e1..1800a2f4 100644
--- a/front/src/primitives/button.tsx
+++ b/front/src/primitives/button.tsx
@@ -5,7 +5,7 @@ import type {
ReactNode,
Ref,
} from "react";
-import { type Falsy, type PressableProps, View } from "react-native";
+import type { Falsy, PressableProps, View } from "react-native";
import { cn } from "~/utils";
import { Icon } from "./icons";
import { PressableFeedback } from "./links";
diff --git a/front/src/primitives/popup.tsx b/front/src/primitives/popup.tsx
index 16fba948..75a8ed0f 100644
--- a/front/src/primitives/popup.tsx
+++ b/front/src/primitives/popup.tsx
@@ -12,12 +12,15 @@ export const Overlay = ({
close,
children,
scroll = true,
+ className,
+ ...props
}: {
icon?: IconType;
title: string;
close?: () => void;
children: ReactNode;
scroll?: boolean;
+ className?: string;
}) => {
return (
}
{scroll ? (
- {children}
+
+ {children}
+
) : (
- {children}
+
+ {children}
+
)}
@@ -54,15 +61,17 @@ export const Popup = ({
close,
children,
scroll,
+ ...props
}: {
icon?: IconType;
title: string;
close?: () => void;
children: ReactNode;
scroll?: boolean;
+ className?: string;
}) => {
return (
-
+
{children}
);
diff --git a/front/src/ui/admin/videos-modal/headers.tsx b/front/src/ui/admin/videos-modal/headers.tsx
index 87a25147..ec246ada 100644
--- a/front/src/ui/admin/videos-modal/headers.tsx
+++ b/front/src/ui/admin/videos-modal/headers.tsx
@@ -101,6 +101,7 @@ export const AddVideoFooter = ({
return (
(
+ // @ts-expect-error prop mismatch due to generic