From f0abd792c7722a7f29ed151d4072e7673c8200bf Mon Sep 17 00:00:00 2001
From: Steven
Date: Thu, 29 Aug 2024 00:06:15 +0800
Subject: [PATCH] chore: update auth service
---
server/router/api/v1/auth_service.go | 23 +++-
web/src/components/PasswordSignInForm.tsx | 125 ++++++++++++++++++++++
web/src/pages/AdminSignIn.tsx | 43 ++++++++
web/src/pages/SignIn.tsx | 116 +-------------------
web/src/router/index.tsx | 5 +
5 files changed, 196 insertions(+), 116 deletions(-)
create mode 100644 web/src/components/PasswordSignInForm.tsx
create mode 100644 web/src/pages/AdminSignIn.tsx
diff --git a/server/router/api/v1/auth_service.go b/server/router/api/v1/auth_service.go
index 1887930e..aef7af83 100644
--- a/server/router/api/v1/auth_service.go
+++ b/server/router/api/v1/auth_service.go
@@ -24,6 +24,10 @@ import (
"github.com/usememos/memos/store"
)
+const (
+ unmatchedEmailAndPasswordError = "unmatched email and password"
+)
+
func (s *APIV1Service) GetAuthStatus(ctx context.Context, _ *v1pb.GetAuthStatusRequest) (*v1pb.User, error) {
user, err := s.GetCurrentUser(ctx)
if err != nil {
@@ -47,14 +51,23 @@ func (s *APIV1Service) SignIn(ctx context.Context, request *v1pb.SignInRequest)
return nil, status.Errorf(codes.Internal, fmt.Sprintf("failed to find user by username %s", request.Username))
}
if user == nil {
- return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("user not found with username %s", request.Username))
- } else if user.RowStatus == store.Archived {
- return nil, status.Errorf(codes.PermissionDenied, fmt.Sprintf("user has been archived with username %s", request.Username))
+ return nil, status.Errorf(codes.InvalidArgument, unmatchedEmailAndPasswordError)
}
-
// Compare the stored hashed password, with the hashed version of the password that was received.
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(request.Password)); err != nil {
- return nil, status.Errorf(codes.InvalidArgument, "unmatched email and password")
+ return nil, status.Errorf(codes.InvalidArgument, unmatchedEmailAndPasswordError)
+ }
+
+ workspaceGeneralSetting, err := s.Store.GetWorkspaceGeneralSetting(ctx)
+ if err != nil {
+ return nil, status.Errorf(codes.Internal, fmt.Sprintf("failed to get workspace general setting, err: %s", err))
+ }
+ // Check if the password sign in is allowed.
+ if workspaceGeneralSetting.DisallowPasswordSignin && user.Role == store.RoleUser {
+ return nil, status.Errorf(codes.PermissionDenied, "password signin is not allowed")
+ }
+ if user.RowStatus == store.Archived {
+ return nil, status.Errorf(codes.PermissionDenied, fmt.Sprintf("user has been archived with username %s", request.Username))
}
expireTime := time.Now().Add(AccessTokenDuration)
diff --git a/web/src/components/PasswordSignInForm.tsx b/web/src/components/PasswordSignInForm.tsx
new file mode 100644
index 00000000..64584333
--- /dev/null
+++ b/web/src/components/PasswordSignInForm.tsx
@@ -0,0 +1,125 @@
+import { Button, Checkbox, Input } from "@mui/joy";
+import { ClientError } from "nice-grpc-web";
+import { useEffect, useState } from "react";
+import { toast } from "react-hot-toast";
+import { authServiceClient } from "@/grpcweb";
+import useLoading from "@/hooks/useLoading";
+import useNavigateTo from "@/hooks/useNavigateTo";
+import { useCommonContext } from "@/layouts/CommonContextProvider";
+import { useUserStore } from "@/store/v1";
+import { useTranslate } from "@/utils/i18n";
+
+const PasswordSignInForm = () => {
+ const t = useTranslate();
+ const navigateTo = useNavigateTo();
+ const commonContext = useCommonContext();
+ const userStore = useUserStore();
+ const actionBtnLoadingState = useLoading(false);
+ const [username, setUsername] = useState("");
+ const [password, setPassword] = useState("");
+ const [remember, setRemember] = useState(true);
+
+ useEffect(() => {
+ if (commonContext.profile.mode === "demo") {
+ setUsername("yourselfhosted");
+ setPassword("yourselfhosted");
+ }
+ }, [commonContext.profile.mode]);
+
+ const handleUsernameInputChanged = (e: React.ChangeEvent) => {
+ const text = e.target.value as string;
+ setUsername(text);
+ };
+
+ const handlePasswordInputChanged = (e: React.ChangeEvent) => {
+ const text = e.target.value as string;
+ setPassword(text);
+ };
+
+ const handleFormSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ handleSignInButtonClick();
+ };
+
+ const handleSignInButtonClick = async () => {
+ if (username === "" || password === "") {
+ return;
+ }
+
+ if (actionBtnLoadingState.isLoading) {
+ return;
+ }
+
+ try {
+ actionBtnLoadingState.setLoading();
+ await authServiceClient.signIn({ username, password, neverExpire: remember });
+ await userStore.fetchCurrentUser();
+ navigateTo("/");
+ } catch (error: any) {
+ console.error(error);
+ toast.error((error as ClientError).details || "Failed to sign in.");
+ }
+ actionBtnLoadingState.setFinish();
+ };
+
+ return (
+
+ );
+};
+
+export default PasswordSignInForm;
diff --git a/web/src/pages/AdminSignIn.tsx b/web/src/pages/AdminSignIn.tsx
new file mode 100644
index 00000000..fae4f69b
--- /dev/null
+++ b/web/src/pages/AdminSignIn.tsx
@@ -0,0 +1,43 @@
+import AppearanceSelect from "@/components/AppearanceSelect";
+import LocaleSelect from "@/components/LocaleSelect";
+import PasswordSignInForm from "@/components/PasswordSignInForm";
+import { useCommonContext } from "@/layouts/CommonContextProvider";
+import { useWorkspaceSettingStore } from "@/store/v1";
+import { WorkspaceGeneralSetting } from "@/types/proto/api/v1/workspace_setting_service";
+import { WorkspaceSettingKey } from "@/types/proto/store/workspace_setting";
+
+const AdminSignIn = () => {
+ const commonContext = useCommonContext();
+ const workspaceSettingStore = useWorkspaceSettingStore();
+ const workspaceGeneralSetting =
+ workspaceSettingStore.getWorkspaceSettingByKey(WorkspaceSettingKey.GENERAL).generalSetting || WorkspaceGeneralSetting.fromPartial({});
+
+ const handleLocaleSelectChange = (locale: Locale) => {
+ commonContext.setLocale(locale);
+ };
+
+ const handleAppearanceSelectChange = (appearance: Appearance) => {
+ commonContext.setAppearance(appearance);
+ };
+
+ return (
+
+
+
+

+
+ {workspaceGeneralSetting.customProfile?.title || "Memos"}
+
+
+
Sign in with admin accounts
+
+
+
+
+ );
+};
+
+export default AdminSignIn;
diff --git a/web/src/pages/SignIn.tsx b/web/src/pages/SignIn.tsx
index 3d44973f..567c7645 100644
--- a/web/src/pages/SignIn.tsx
+++ b/web/src/pages/SignIn.tsx
@@ -1,16 +1,14 @@
-import { Button, Checkbox, Divider, Input } from "@mui/joy";
-import { ClientError } from "nice-grpc-web";
+import { Button, Divider } from "@mui/joy";
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { Link } from "react-router-dom";
import AppearanceSelect from "@/components/AppearanceSelect";
import LocaleSelect from "@/components/LocaleSelect";
-import { authServiceClient, identityProviderServiceClient } from "@/grpcweb";
+import PasswordSignInForm from "@/components/PasswordSignInForm";
+import { identityProviderServiceClient } from "@/grpcweb";
import { absolutifyLink } from "@/helpers/utils";
-import useLoading from "@/hooks/useLoading";
-import useNavigateTo from "@/hooks/useNavigateTo";
import { useCommonContext } from "@/layouts/CommonContextProvider";
-import { extractIdentityProviderIdFromName, useUserStore, useWorkspaceSettingStore } from "@/store/v1";
+import { extractIdentityProviderIdFromName, useWorkspaceSettingStore } from "@/store/v1";
import { IdentityProvider, IdentityProvider_Type } from "@/types/proto/api/v1/idp_service";
import { WorkspaceGeneralSetting } from "@/types/proto/api/v1/workspace_setting_service";
import { WorkspaceSettingKey } from "@/types/proto/store/workspace_setting";
@@ -18,14 +16,8 @@ import { useTranslate } from "@/utils/i18n";
const SignIn = () => {
const t = useTranslate();
- const navigateTo = useNavigateTo();
const commonContext = useCommonContext();
const workspaceSettingStore = useWorkspaceSettingStore();
- const userStore = useUserStore();
- const actionBtnLoadingState = useLoading(false);
- const [username, setUsername] = useState("");
- const [password, setPassword] = useState("");
- const [remember, setRemember] = useState(true);
const [identityProviderList, setIdentityProviderList] = useState([]);
const workspaceGeneralSetting =
workspaceSettingStore.getWorkspaceSettingByKey(WorkspaceSettingKey.GENERAL).generalSetting || WorkspaceGeneralSetting.fromPartial({});
@@ -38,23 +30,6 @@ const SignIn = () => {
fetchIdentityProviderList();
}, []);
- useEffect(() => {
- if (commonContext.profile.mode === "demo") {
- setUsername("yourselfhosted");
- setPassword("yourselfhosted");
- }
- }, [commonContext.profile.mode]);
-
- const handleUsernameInputChanged = (e: React.ChangeEvent) => {
- const text = e.target.value as string;
- setUsername(text);
- };
-
- const handlePasswordInputChanged = (e: React.ChangeEvent) => {
- const text = e.target.value as string;
- setPassword(text);
- };
-
const handleLocaleSelectChange = (locale: Locale) => {
commonContext.setLocale(locale);
};
@@ -63,32 +38,6 @@ const SignIn = () => {
commonContext.setAppearance(appearance);
};
- const handleFormSubmit = (e: React.FormEvent) => {
- e.preventDefault();
- handleSignInButtonClick();
- };
-
- const handleSignInButtonClick = async () => {
- if (username === "" || password === "") {
- return;
- }
-
- if (actionBtnLoadingState.isLoading) {
- return;
- }
-
- try {
- actionBtnLoadingState.setLoading();
- await authServiceClient.signIn({ username, password, neverExpire: remember });
- await userStore.fetchCurrentUser();
- navigateTo("/");
- } catch (error: any) {
- console.error(error);
- toast.error((error as ClientError).details || "Failed to sign in.");
- }
- actionBtnLoadingState.setFinish();
- };
-
const handleSignInWithIdentityProvider = async (identityProvider: IdentityProvider) => {
const stateQueryParameter = `auth.signin.${identityProvider.title}-${extractIdentityProviderIdFromName(identityProvider.name)}`;
if (identityProvider.type === IdentityProvider_Type.OAUTH2) {
@@ -117,62 +66,7 @@ const SignIn = () => {
{!workspaceGeneralSetting.disallowPasswordSignin ? (
-
+
) : (
Password auth is not allowed.
)}
diff --git a/web/src/router/index.tsx b/web/src/router/index.tsx
index 6cacedf3..6be39e6b 100644
--- a/web/src/router/index.tsx
+++ b/web/src/router/index.tsx
@@ -3,6 +3,7 @@ import App from "@/App";
import RootLayout from "@/layouts/RootLayout";
import SuspenseWrapper from "@/layouts/SuspenseWrapper";
import About from "@/pages/About";
+import AdminSignIn from "@/pages/AdminSignIn";
import Archived from "@/pages/Archived";
import AuthCallback from "@/pages/AuthCallback";
import Explore from "@/pages/Explore";
@@ -41,6 +42,10 @@ const router = createBrowserRouter([
path: "",
element: ,
},
+ {
+ path: "admin",
+ element: ,
+ },
{
path: "signup",
element: ,