chore: use auth api v1 in frontend

This commit is contained in:
Steven 2023-06-22 19:11:59 +08:00
parent 0296e5b57f
commit f67e0adea4
7 changed files with 45 additions and 103 deletions

View File

@ -20,12 +20,12 @@ func getUserIDContextKey() string {
return userIDContextKey return userIDContextKey
} }
type SignUpRequest struct { type SignInRequest struct {
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
} }
type SignInRequest struct { type SignUpRequest struct {
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
} }
@ -52,8 +52,7 @@ func (s *APIV1Service) registerAuthRoutes(g *echo.Group, secret string) {
// Compare the stored hashed password, with the hashed version of the password that was received. // Compare the stored hashed password, with the hashed version of the password that was received.
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(signin.Password)); err != nil { if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(signin.Password)); err != nil {
// If the two passwords don't match, return a 401 status. return echo.NewHTTPError(http.StatusUnauthorized, "Unmatched username and password").SetInternal(err)
return echo.NewHTTPError(http.StatusUnauthorized, "Incorrect password").SetInternal(err)
} }
if err := auth.GenerateTokensAndSetCookies(c, user, secret); err != nil { if err := auth.GenerateTokensAndSetCookies(c, user, secret); err != nil {
@ -69,28 +68,28 @@ func (s *APIV1Service) registerAuthRoutes(g *echo.Group, secret string) {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted signup request").SetInternal(err) return echo.NewHTTPError(http.StatusBadRequest, "Malformatted signup request").SetInternal(err)
} }
user := &store.User{
Username: signup.Username,
Nickname: signup.Username,
}
passwordHash, err := bcrypt.GenerateFromPassword([]byte(signup.Password), bcrypt.DefaultCost) passwordHash, err := bcrypt.GenerateFromPassword([]byte(signup.Password), bcrypt.DefaultCost)
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate password hash").SetInternal(err) return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate password hash").SetInternal(err)
} }
user.PasswordHash = string(passwordHash)
create := &store.User{
Username: signup.Username,
Nickname: signup.Username,
PasswordHash: string(passwordHash),
}
existingUsers, err := s.Store.ListUsers(ctx, &store.FindUser{}) existingUsers, err := s.Store.ListUsers(ctx, &store.FindUser{})
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find existing users").SetInternal(err) return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find existing users").SetInternal(err)
} }
// The first user to sign up is an admin by default. // The first user to sign up is an admin by default.
if len(existingUsers) == 0 { if len(existingUsers) == 0 {
user.Role = store.RoleAdmin create.Role = store.RoleAdmin
} else { } else {
user.Role = store.RoleUser create.Role = store.RoleUser
} }
user, err = s.Store.CreateUser(ctx, user) user, err := s.Store.CreateUser(ctx, create)
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create user").SetInternal(err) return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create user").SetInternal(err)
} }

View File

@ -1,56 +1,45 @@
import axios from "axios"; import axios from "axios";
type ResponseObject<T> = {
data: T;
error?: string;
message?: string;
};
export function getSystemStatus() { export function getSystemStatus() {
return axios.get<ResponseObject<SystemStatus>>("/api/status"); return axios.get<SystemStatus>("/api/v1/status");
} }
export function signin(email: string, password: string) { export function signin(username: string, password: string) {
return axios.post<ResponseObject<User>>("/api/auth/signin", { return axios.post<User>("/api/v1/auth/signin", {
email, username,
password, password,
}); });
} }
export function signup(email: string, password: string) { export function signup(username: string, password: string) {
return axios.post<ResponseObject<User>>("/api/auth/signup", { return axios.post<User>("/api/v1/auth/signup", {
email, username,
password, password,
name: email,
}); });
} }
export function signout() { export function signout() {
return axios.post("/api/auth/logout"); return axios.post("/api/v1/auth/logout");
}
export function createUser(userCreate: UserCreate) {
return axios.post<ResponseObject<User>>("/api/user", userCreate);
} }
export function getMyselfUser() { export function getMyselfUser() {
return axios.get<ResponseObject<User>>("/api/user/me"); return axios.get<User>("/api/v1/user/me");
} }
export function getUserList() { export function getUserList() {
return axios.get<ResponseObject<User[]>>("/api/user"); return axios.get<User[]>("/api/v1/user");
} }
export function getUserById(id: number) { export function getUserById(id: number) {
return axios.get<ResponseObject<User>>(`/api/user/${id}`); return axios.get<User>(`/api/v1/user/${id}`);
} }
export function patchUser(userPatch: UserPatch) { export function patchUser(userPatch: UserPatch) {
return axios.patch<ResponseObject<User>>(`/api/user/${userPatch.id}`, userPatch); return axios.patch<User>(`/api/v1/user/${userPatch.id}`, userPatch);
} }
export function deleteUser(userDelete: UserDelete) { export function deleteUser(userDelete: UserDelete) {
return axios.delete(`/api/user/${userDelete.id}`); return axios.delete(`/api/v1/user/${userDelete.id}`);
} }
export function getShortcutList(shortcutFind?: ShortcutFind) { export function getShortcutList(shortcutFind?: ShortcutFind) {
@ -58,21 +47,21 @@ export function getShortcutList(shortcutFind?: ShortcutFind) {
if (shortcutFind?.creatorId) { if (shortcutFind?.creatorId) {
queryList.push(`creatorId=${shortcutFind.creatorId}`); queryList.push(`creatorId=${shortcutFind.creatorId}`);
} }
return axios.get<ResponseObject<Shortcut[]>>(`/api/shortcut?${queryList.join("&")}`); return axios.get<Shortcut[]>(`/api/v1/shortcut?${queryList.join("&")}`);
} }
export function getShortcutWithNameAndWorkspaceName(workspaceName: string, shortcutName: string) { export function getShortcutWithNameAndWorkspaceName(workspaceName: string, shortcutName: string) {
return axios.get<ResponseObject<Shortcut>>(`/api/workspace/${workspaceName}/shortcut/${shortcutName}`); return axios.get<Shortcut>(`/api/v1/workspace/${workspaceName}/shortcut/${shortcutName}`);
} }
export function createShortcut(shortcutCreate: ShortcutCreate) { export function createShortcut(shortcutCreate: ShortcutCreate) {
return axios.post<ResponseObject<Shortcut>>("/api/shortcut", shortcutCreate); return axios.post<Shortcut>("/api/v1/shortcut", shortcutCreate);
} }
export function patchShortcut(shortcutPatch: ShortcutPatch) { export function patchShortcut(shortcutPatch: ShortcutPatch) {
return axios.patch<ResponseObject<Shortcut>>(`/api/shortcut/${shortcutPatch.id}`, shortcutPatch); return axios.patch<Shortcut>(`/api/v1/shortcut/${shortcutPatch.id}`, shortcutPatch);
} }
export function deleteShortcutById(shortcutId: ShortcutId) { export function deleteShortcutById(shortcutId: ShortcutId) {
return axios.delete(`/api/shortcut/${shortcutId}`); return axios.delete(`/api/v1/shortcut/${shortcutId}`);
} }

View File

@ -3,21 +3,13 @@ import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import * as api from "../helpers/api"; import * as api from "../helpers/api";
import { validate, ValidatorConfig } from "../helpers/validator";
import { userService } from "../services"; import { userService } from "../services";
import useLoading from "../hooks/useLoading"; import useLoading from "../hooks/useLoading";
import Icon from "../components/Icon"; import Icon from "../components/Icon";
const validateConfig: ValidatorConfig = {
minLength: 3,
maxLength: 24,
noSpace: true,
noChinese: true,
};
const Auth: React.FC = () => { const Auth: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [email, setEmail] = useState(""); const [username, setUsername] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const actionBtnLoadingState = useLoading(false); const actionBtnLoadingState = useLoading(false);
@ -26,19 +18,11 @@ const Auth: React.FC = () => {
navigate("/"); navigate("/");
return; return;
} }
api.getSystemStatus().then(({ data }) => {
const { data: status } = data;
if (status.profile.mode === "dev") {
setEmail("frank@shortify.demo");
setPassword("secret");
}
});
}, []); }, []);
const handleEmailInputChanged = (e: React.ChangeEvent<HTMLInputElement>) => { const handleUsernameInputChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
const text = e.target.value as string; const text = e.target.value as string;
setEmail(text); setUsername(text);
}; };
const handlePasswordInputChanged = (e: React.ChangeEvent<HTMLInputElement>) => { const handlePasswordInputChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
@ -51,21 +35,9 @@ const Auth: React.FC = () => {
return; return;
} }
const emailValidResult = validate(email, validateConfig);
if (!emailValidResult.result) {
toast.error("Email: " + emailValidResult.reason);
return;
}
const passwordValidResult = validate(password, validateConfig);
if (!passwordValidResult.result) {
toast.error("Password: " + passwordValidResult.reason);
return;
}
try { try {
actionBtnLoadingState.setLoading(); actionBtnLoadingState.setLoading();
await api.signin(email, password); await api.signin(username, password);
const user = await userService.doSignIn(); const user = await userService.doSignIn();
if (user) { if (user) {
navigate("/", { navigate("/", {
@ -86,21 +58,9 @@ const Auth: React.FC = () => {
return; return;
} }
const emailValidResult = validate(email, validateConfig);
if (!emailValidResult.result) {
toast.error("Email: " + emailValidResult.reason);
return;
}
const passwordValidResult = validate(password, validateConfig);
if (!passwordValidResult.result) {
toast.error("Password: " + passwordValidResult.reason);
return;
}
try { try {
actionBtnLoadingState.setLoading(); actionBtnLoadingState.setLoading();
await api.signup(email, password); await api.signup(username, password);
const user = await userService.doSignIn(); const user = await userService.doSignIn();
if (user) { if (user) {
navigate("/", { navigate("/", {
@ -129,8 +89,8 @@ const Auth: React.FC = () => {
</div> </div>
<div className={`flex flex-col justify-start items-start w-full ${actionBtnLoadingState.isLoading ? "opacity-80" : ""}`}> <div className={`flex flex-col justify-start items-start w-full ${actionBtnLoadingState.isLoading ? "opacity-80" : ""}`}>
<div className="w-full flex flex-col mb-2"> <div className="w-full flex flex-col mb-2">
<span className="leading-8 mb-1 text-gray-600">Email</span> <span className="leading-8 mb-1 text-gray-600">Username</span>
<Input className="w-full py-3" type="email" value={email} onChange={handleEmailInputChanged} /> <Input className="w-full py-3" type="username" value={username} onChange={handleUsernameInputChanged} />
</div> </div>
<div className="w-full flex flex-col mb-2"> <div className="w-full flex flex-col mb-2">
<span className="leading-8 text-gray-600">Password</span> <span className="leading-8 text-gray-600">Password</span>

View File

@ -16,7 +16,7 @@ const ShortcutRedirector: React.FC = () => {
const workspaceName = params.workspaceName || ""; const workspaceName = params.workspaceName || "";
const shortcutName = params.shortcutName || ""; const shortcutName = params.shortcutName || "";
getShortcutWithNameAndWorkspaceName(workspaceName, shortcutName) getShortcutWithNameAndWorkspaceName(workspaceName, shortcutName)
.then(({ data: { data: shortcut } }) => { .then(({ data: shortcut }) => {
if (shortcut) { if (shortcut) {
window.location.href = shortcut.link; window.location.href = shortcut.link;
} else { } else {

View File

@ -16,14 +16,14 @@ const shortcutService = {
}, },
fetchWorkspaceShortcuts: async () => { fetchWorkspaceShortcuts: async () => {
const { data } = (await api.getShortcutList({})).data; const data = (await api.getShortcutList({})).data;
const shortcuts = data.map((s) => convertResponseModelShortcut(s)); const shortcuts = data.map((s) => convertResponseModelShortcut(s));
store.dispatch(setShortcuts(shortcuts)); store.dispatch(setShortcuts(shortcuts));
return shortcuts; return shortcuts;
}, },
getMyAllShortcuts: async () => { getMyAllShortcuts: async () => {
const { data } = (await api.getShortcutList()).data; const data = (await api.getShortcutList()).data;
const shortcuts = data.map((s) => convertResponseModelShortcut(s)); const shortcuts = data.map((s) => convertResponseModelShortcut(s));
store.dispatch(setShortcuts(shortcuts)); store.dispatch(setShortcuts(shortcuts));
}, },
@ -39,13 +39,13 @@ const shortcutService = {
}, },
createShortcut: async (shortcutCreate: ShortcutCreate) => { createShortcut: async (shortcutCreate: ShortcutCreate) => {
const { data } = (await api.createShortcut(shortcutCreate)).data; const data = (await api.createShortcut(shortcutCreate)).data;
const shortcut = convertResponseModelShortcut(data); const shortcut = convertResponseModelShortcut(data);
store.dispatch(createShortcut(shortcut)); store.dispatch(createShortcut(shortcut));
}, },
patchShortcut: async (shortcutPatch: ShortcutPatch) => { patchShortcut: async (shortcutPatch: ShortcutPatch) => {
const { data } = (await api.patchShortcut(shortcutPatch)).data; const data = (await api.patchShortcut(shortcutPatch)).data;
const shortcut = convertResponseModelShortcut(data); const shortcut = convertResponseModelShortcut(data);
store.dispatch(patchShortcut(shortcut)); store.dispatch(patchShortcut(shortcut));
}, },

View File

@ -17,7 +17,7 @@ const userService = {
initialState: async () => { initialState: async () => {
try { try {
const { data: user } = (await api.getMyselfUser()).data; const user = (await api.getMyselfUser()).data;
if (user) { if (user) {
store.dispatch(setUser(convertResponseModelUser(user))); store.dispatch(setUser(convertResponseModelUser(user)));
} }
@ -27,7 +27,7 @@ const userService = {
}, },
doSignIn: async () => { doSignIn: async () => {
const { data: user } = (await api.getMyselfUser()).data; const user = (await api.getMyselfUser()).data;
if (user) { if (user) {
store.dispatch(setUser(convertResponseModelUser(user))); store.dispatch(setUser(convertResponseModelUser(user)));
} else { } else {
@ -42,7 +42,7 @@ const userService = {
}, },
getUserById: async (userId: UserId) => { getUserById: async (userId: UserId) => {
const { data: user } = (await api.getUserById(userId)).data; const user = (await api.getUserById(userId)).data;
if (user) { if (user) {
return convertResponseModelUser(user); return convertResponseModelUser(user);
} else { } else {
@ -51,7 +51,7 @@ const userService = {
}, },
patchUser: async (userPatch: UserPatch): Promise<void> => { patchUser: async (userPatch: UserPatch): Promise<void> => {
const { data } = (await api.patchUser(userPatch)).data; const data = (await api.patchUser(userPatch)).data;
if (userPatch.id === store.getState().user.user?.id) { if (userPatch.id === store.getState().user.user?.id) {
const user = convertResponseModelUser(data); const user = convertResponseModelUser(data);
store.dispatch(patchUser(user)); store.dispatch(patchUser(user));

View File

@ -15,12 +15,6 @@ interface User {
role: Role; role: Role;
} }
interface UserCreate {
email: string;
password: string;
displayName: string;
}
interface UserPatch { interface UserPatch {
id: UserId; id: UserId;