Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
f0abd792
Commit
f0abd792
authored
Aug 28, 2024
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: update auth service
parent
1167df29
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
196 additions
and
116 deletions
+196
-116
auth_service.go
server/router/api/v1/auth_service.go
+18
-5
PasswordSignInForm.tsx
web/src/components/PasswordSignInForm.tsx
+125
-0
AdminSignIn.tsx
web/src/pages/AdminSignIn.tsx
+43
-0
SignIn.tsx
web/src/pages/SignIn.tsx
+5
-111
index.tsx
web/src/router/index.tsx
+5
-0
No files found.
server/router/api/v1/auth_service.go
View file @
f0abd792
...
...
@@ -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
)
...
...
web/src/components/PasswordSignInForm.tsx
0 → 100644
View file @
f0abd792
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
<
HTMLInputElement
>
)
=>
{
const
text
=
e
.
target
.
value
as
string
;
setUsername
(
text
);
};
const
handlePasswordInputChanged
=
(
e
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
const
text
=
e
.
target
.
value
as
string
;
setPassword
(
text
);
};
const
handleFormSubmit
=
(
e
:
React
.
FormEvent
<
HTMLFormElement
>
)
=>
{
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
(
<
form
className=
"w-full mt-2"
onSubmit=
{
handleFormSubmit
}
>
<
div
className=
"flex flex-col justify-start items-start w-full gap-4"
>
<
div
className=
"w-full flex flex-col justify-start items-start"
>
<
span
className=
"leading-8 text-gray-600"
>
{
t
(
"common.username"
)
}
</
span
>
<
Input
className=
"w-full"
size=
"lg"
type=
"text"
readOnly=
{
actionBtnLoadingState
.
isLoading
}
placeholder=
{
t
(
"common.username"
)
}
value=
{
username
}
autoComplete=
"username"
autoCapitalize=
"off"
spellCheck=
{
false
}
onChange=
{
handleUsernameInputChanged
}
required
/>
</
div
>
<
div
className=
"w-full flex flex-col justify-start items-start"
>
<
span
className=
"leading-8 text-gray-600"
>
{
t
(
"common.password"
)
}
</
span
>
<
Input
className=
"w-full"
size=
"lg"
type=
"password"
readOnly=
{
actionBtnLoadingState
.
isLoading
}
placeholder=
{
t
(
"common.password"
)
}
value=
{
password
}
autoComplete=
"password"
autoCapitalize=
"off"
spellCheck=
{
false
}
onChange=
{
handlePasswordInputChanged
}
required
/>
</
div
>
</
div
>
<
div
className=
"flex flex-row justify-start items-center w-full mt-6"
>
<
Checkbox
className=
"dark:!text-gray-400"
label=
{
t
(
"common.remember-me"
)
}
checked=
{
remember
}
onChange=
{
(
e
)
=>
setRemember
(
e
.
target
.
checked
)
}
/>
</
div
>
<
div
className=
"flex flex-row justify-end items-center w-full mt-6"
>
<
Button
className=
"w-full"
size=
"md"
type=
"submit"
disabled=
{
actionBtnLoadingState
.
isLoading
}
loading=
{
actionBtnLoadingState
.
isLoading
}
onClick=
{
handleSignInButtonClick
}
>
{
t
(
"common.sign-in"
)
}
</
Button
>
</
div
>
</
form
>
);
};
export
default
PasswordSignInForm
;
web/src/pages/AdminSignIn.tsx
0 → 100644
View file @
f0abd792
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
(
<
div
className=
"py-4 sm:py-8 w-80 max-w-full min-h-[100svh] mx-auto flex flex-col justify-start items-center"
>
<
div
className=
"w-full py-4 grow flex flex-col justify-center items-center"
>
<
div
className=
"w-full flex flex-row justify-center items-center mb-6"
>
<
img
className=
"h-14 w-auto rounded-full shadow"
src=
{
workspaceGeneralSetting
.
customProfile
?.
logoUrl
||
"/logo.webp"
}
alt=
""
/>
<
p
className=
"ml-2 text-5xl text-black opacity-80 dark:text-gray-200"
>
{
workspaceGeneralSetting
.
customProfile
?.
title
||
"Memos"
}
</
p
>
</
div
>
<
p
className=
"w-full text-xl font-medium"
>
Sign in with admin accounts
</
p
>
<
PasswordSignInForm
/>
</
div
>
<
div
className=
"mt-4 flex flex-row items-center justify-center w-full gap-2"
>
<
LocaleSelect
value=
{
commonContext
.
locale
}
onChange=
{
handleLocaleSelectChange
}
/>
<
AppearanceSelect
value=
{
commonContext
.
appearance
as
Appearance
}
onChange=
{
handleAppearanceSelectChange
}
/>
</
div
>
</
div
>
);
};
export
default
AdminSignIn
;
web/src/pages/SignIn.tsx
View file @
f0abd792
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
,
use
UserStore
,
use
WorkspaceSettingStore
}
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
<
IdentityProvider
[]
>
([]);
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
<
HTMLInputElement
>
)
=>
{
const
text
=
e
.
target
.
value
as
string
;
setUsername
(
text
);
};
const
handlePasswordInputChanged
=
(
e
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
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
<
HTMLFormElement
>
)
=>
{
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 = () => {
</
p
>
</
div
>
{
!
workspaceGeneralSetting
.
disallowPasswordSignin
?
(
<
form
className=
"w-full mt-2"
onSubmit=
{
handleFormSubmit
}
>
<
div
className=
"flex flex-col justify-start items-start w-full gap-4"
>
<
div
className=
"w-full flex flex-col justify-start items-start"
>
<
span
className=
"leading-8 text-gray-600"
>
{
t
(
"common.username"
)
}
</
span
>
<
Input
className=
"w-full"
size=
"lg"
type=
"text"
readOnly=
{
actionBtnLoadingState
.
isLoading
}
placeholder=
{
t
(
"common.username"
)
}
value=
{
username
}
autoComplete=
"username"
autoCapitalize=
"off"
spellCheck=
{
false
}
onChange=
{
handleUsernameInputChanged
}
required
/>
</
div
>
<
div
className=
"w-full flex flex-col justify-start items-start"
>
<
span
className=
"leading-8 text-gray-600"
>
{
t
(
"common.password"
)
}
</
span
>
<
Input
className=
"w-full"
size=
"lg"
type=
"password"
readOnly=
{
actionBtnLoadingState
.
isLoading
}
placeholder=
{
t
(
"common.password"
)
}
value=
{
password
}
autoComplete=
"password"
autoCapitalize=
"off"
spellCheck=
{
false
}
onChange=
{
handlePasswordInputChanged
}
required
/>
</
div
>
</
div
>
<
div
className=
"flex flex-row justify-start items-center w-full mt-6"
>
<
Checkbox
className=
"dark:!text-gray-400"
label=
{
t
(
"common.remember-me"
)
}
checked=
{
remember
}
onChange=
{
(
e
)
=>
setRemember
(
e
.
target
.
checked
)
}
/>
</
div
>
<
div
className=
"flex flex-row justify-end items-center w-full mt-6"
>
<
Button
className=
"w-full"
size=
"md"
type=
"submit"
disabled=
{
actionBtnLoadingState
.
isLoading
}
loading=
{
actionBtnLoadingState
.
isLoading
}
onClick=
{
handleSignInButtonClick
}
>
{
t
(
"common.sign-in"
)
}
</
Button
>
</
div
>
</
form
>
<
PasswordSignInForm
/>
)
:
(
<
p
className=
"w-full text-2xl mt-2 dark:text-gray-500"
>
Password auth is not allowed.
</
p
>
)
}
...
...
web/src/router/index.tsx
View file @
f0abd792
...
...
@@ -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
:
<
SignIn
/>,
},
{
path
:
"admin"
,
element
:
<
AdminSignIn
/>,
},
{
path
:
"signup"
,
element
:
<
SignUp
/>,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment