Skip to content

Commit

Permalink
feat(desktop): signup api (#4314)
Browse files Browse the repository at this point in the history
  • Loading branch information
xudaotutou authored Nov 21, 2023
1 parent 5c59030 commit 7c08451
Show file tree
Hide file tree
Showing 19 changed files with 9,588 additions and 13,150 deletions.
2 changes: 2 additions & 0 deletions frontend/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"test:e2e": "jest --testPathPattern=/e2e/ --runInBand",
"test:e2e-namespace": "jest --testPathPattern=/e2e/namespace/ --runInBand",
"test:e2e-auth": "jest --testPathPattern=/e2e/auth/ --runInBand",
"test:e2e-api": "jest --testPathPattern=/e2e/v1alpha/ --runInBand",
"test:ci": "jest --runInBand"
},
"dependencies": {
Expand Down Expand Up @@ -46,6 +47,7 @@
"next-pwa": "^5.6.0",
"nprogress": "^0.2.0",
"qrcode.react": "^3.1.0",
"randexp": "^0.5.3",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-draggable": "^4.4.6",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe('Login create', () => {
const res = await createRequest({ teamName: 'hello' });
expect(res.code).toBe(409);
});
it.each(new Array(getTeamLimit() + 2).map((_, idx) => [`team${idx}`, idx]))(
it.skip.each(new Array(getTeamLimit() + 2).map((_, idx) => [`team${idx}`, idx]))(
'limit 4 team',
async (teamName: string, idx: number) => {
if (idx === 0) {
Expand Down
64 changes: 64 additions & 0 deletions frontend/desktop/src/__tests__/api/e2e/v1alpha/signup.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Session } from 'sealos-desktop-sdk/*';
import * as k8s from '@kubernetes/client-node';
import { _passwordLoginRequest, _passwordModifyRequest } from '@/api/auth';
import { _setAuth, cleanDb, cleanK8s } from '@/__tests__/api/tools';
import { _createRequest } from '@/api/namespace';
import request from '@/__tests__/api/request';
import { Db, MongoClient } from 'mongodb';
import { ApiResp } from '@/types';
import RandExp from 'randexp';

export const getUserAndPassword = () => {
const userFactory = new RandExp('[a-zA-Z0-9_-]{10,16}');
const password = new RandExp(/\w{8,20}/).gen();
return {
username: userFactory.gen(),
password
};
};
describe('v1alpha/signup', () => {
const generateRequest = (data: { username: string; password: string }) =>
request.post<any, ApiResp<Omit<Session, 'token'>>>('/api/v1alpha/password/signup', data);
const signinRequest = (data: { username: string; password: string }) =>
request.post<typeof data, ApiResp<Omit<Session, 'token'>>>(
'/api/v1alpha/password/signin',
data
);
let db: Db;
let connection: MongoClient;
const setAuth = _setAuth(request);
beforeAll(async () => {
//@ts-ignore
const uri = process.env.MONGODB_URI as string;
connection = new MongoClient(uri);
await connection.connect();
db = connection.db();
const kc = new k8s.KubeConfig();
await cleanK8s(kc, db);
await cleanDb(db);
}, 100000);
afterAll(async () => {
await connection.close();
});
it.each([[1], [2], [3], [4], [5]])(
'sign up test',
async (time) => {
const user = getUserAndPassword();
if (!user) return;
expect.assertions(2);
const genRes = await generateRequest(user);
expect(genRes.data).toBeDefined();
console.log(`the ${time} times`);
console.log(`session`, genRes.data);
if (genRes.code === 200) {
const res = await signinRequest(user);
expect(res.data?.kubeconfig).toBe(genRes.data?.kubeconfig);
} else {
const res = await signinRequest(user);
expect(res.code).toBe(403);
}
setAuth({});
},
10000
);
});
11 changes: 6 additions & 5 deletions frontend/desktop/src/__tests__/api/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ export const cleanDb = async (db: Db) => {
);
};

export const _setAuth = (request: AxiosInstance) => (session: Session) => {
export const _setAuth = (request: AxiosInstance) => (session: Partial<Session>) => {
request.interceptors.request.clear();
request.interceptors.request.use((config) => {
config.headers.Authorization = session?.token;
return config;
});
session?.token &&
request.interceptors.request.use((config) => {
config.headers.Authorization = session.token;
return config;
});
};
23 changes: 12 additions & 11 deletions frontend/desktop/src/pages/api/auth/password/exist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { queryUser } from '@/services/backend/db/user';
import { hashPassword } from '@/utils/crypto';
import { TUserExist } from '@/types/user';
import { enablePassword, enableSignUp } from '@/services/enable';
import { passwrodUserIsExist } from '@/services/backend/oauth';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
if (!enablePassword()) {
Expand All @@ -21,8 +22,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
});
}
const result = await queryUser({ id: user, provider: 'password_user' });
if (!result || !result.password || result.password === hashPassword('')) {
const isExist = await passwrodUserIsExist({ username: user });
if (!isExist)
return jsonRes<TUserExist>(res, {
message: 'user not found',
code: 201,
Expand All @@ -31,15 +32,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
exist: false
}
});
}
return jsonRes<TUserExist>(res, {
code: 200,
message: 'Successfully',
data: {
user,
exist: true
}
});
else
return jsonRes<TUserExist>(res, {
code: 200,
message: 'Successfully',
data: {
user,
exist: true
}
});
} catch (err) {
console.log(err);
return jsonRes(res, {
Expand Down
53 changes: 53 additions & 0 deletions frontend/desktop/src/pages/api/v1alpha/password/signin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/services/backend/response';
import { ApiSession, Session } from '@/types/session';
import { signInByPassword } from '@/services/backend/oauth';
import { enableApi, enablePassword } from '@/services/enable';
import { getUserKubeconfig } from '@/services/backend/kubernetes/admin';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
if (!enablePassword()) {
throw new Error('PASSWORD_SALT is not defined');
}
if (!enableApi()) throw new Error('Failed to sign in by api');
const { password, username } = req.body as Record<string, string>;
if (!password) return jsonRes(res, { code: 400, message: 'password is Required' });
if (!username) return jsonRes(res, { code: 400, message: 'username is Required' });
const signResult = await signInByPassword({
username,
password
});
if (!signResult)
return jsonRes(res, {
code: 403,
message: 'user is invaild'
});
const { k8s_user, namespace, user } = signResult;
const kubernetesUsername = k8s_user.name;
const kubeconfig = await getUserKubeconfig(user.uid, kubernetesUsername);
if (!kubeconfig) {
throw new Error('Failed to get user config');
}
return jsonRes<ApiSession>(res, {
data: {
user: {
name: user.name,
kubernetesUsername,
avatar: user.avatar_url,
nsID: namespace.id,
nsUID: namespace.uid,
userID: user.uid
},
kubeconfig
},
code: 200,
message: 'Successfully'
});
} catch (err) {
console.log(err);
return jsonRes(res, {
message: 'Failed to authenticate with password',
code: 500
});
}
}
67 changes: 67 additions & 0 deletions frontend/desktop/src/pages/api/v1alpha/password/signup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/services/backend/response';
import { ApiSession, Session } from '@/types/session';
import { passwrodUserIsExist, signUpByPassword } from '@/services/backend/oauth';
import { enablePassword, enableApi, enableSignUp } from '@/services/enable';
import { getUserKubeconfig } from '@/services/backend/kubernetes/admin';
import { strongPassword } from '@/utils/crypto';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
if (!enablePassword()) {
throw new Error('PASSWORD_SALT is not defined');
}
if (!enableSignUp()) throw new Error('Failed to sign up user');
if (!enableApi()) throw new Error('Failed to sign up by api');
const { password, username } = req.body as Record<string, string>;
if (!password) return jsonRes(res, { code: 400, message: 'password is Required' });
if (!username) return jsonRes(res, { code: 400, message: 'username is Required' });
// const userAndPassword = await getUserAndPassword();
// if (!userAndPassword) throw Error('Failed to generate user ');
// const { username, password } = userAndPassword;
if (!strongPassword(password)) {
return jsonRes(res, {
message:
'Password must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one number and one special character',
code: 400
});
}
const isExist = await passwrodUserIsExist({ username });
if (isExist)
return jsonRes(res, {
message: 'User is already exist',
code: 409
});
const signResult = await signUpByPassword({
username,
password
});
if (!signResult) throw new Error('Failed to edit db');
const { k8s_user, namespace, user } = signResult;
const kubernetesUsername = k8s_user.name;
const kubeconfig = await getUserKubeconfig(user.uid, kubernetesUsername);
if (!kubeconfig) {
throw new Error('Failed to get user config');
}
return jsonRes<ApiSession>(res, {
data: {
user: {
name: user.name,
kubernetesUsername,
avatar: user.avatar_url,
nsID: namespace.id,
nsUID: namespace.uid,
userID: user.uid
},
kubeconfig
},
code: 200,
message: 'Successfully'
});
} catch (err) {
console.log(err);
return jsonRes(res, {
message: 'Failed to authenticate with password',
code: 500
});
}
}
1 change: 0 additions & 1 deletion frontend/desktop/src/services/backend/kubernetes/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ async function watchClusterObject({
try {
const data = await client.getClusterCustomObjectStatus(group, version, plural, name);
body = data.body;
console.log(body);
if (
'status' in body &&
// @ts-ignore
Expand Down
38 changes: 35 additions & 3 deletions frontend/desktop/src/services/backend/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { v4 as uuid } from 'uuid';
import { createUTN, queryUTN } from './db/userToNamespace';
import { InvitedStatus, NSType, UserRole } from '@/types/team';
import { enableSignUp } from '../enable';
import RandExp from 'randexp';
import { reject } from 'lodash';

export const getOauthRes = async ({
provider,
Expand Down Expand Up @@ -73,6 +75,7 @@ export const getOauthRes = async ({
kubeconfig
};
};

async function signIn({
userResult: _user,
provider,
Expand Down Expand Up @@ -140,7 +143,6 @@ async function signIn({
namespace
};
}

async function signUp({
provider,
id,
Expand All @@ -155,8 +157,6 @@ async function signUp({
password?: string;
}) {
const ns_uid = uuid();
if (provider === 'password_user') {
}
let user: User | null = null;
if (provider === 'password_user') {
if (!password) return null;
Expand Down Expand Up @@ -191,3 +191,35 @@ async function signUp({
namespace
};
}
export function signUpByPassword({ password, username }: { password: string; username: string }) {
return signUp({
provider: 'password_user',
id: username,
name: username,
avatar_url: '',
password
});
}
export async function signInByPassword({
password,
username
}: {
password: string;
username: string;
}) {
const _user = await queryUser({ id: username, provider: 'password_user' });
if (!_user) return null;
return signIn({
userResult: _user,
provider: 'password_user',
password,
id: username
}).then(
(v) => v,
(_) => Promise.resolve(null)
);
}
export async function passwrodUserIsExist({ username: id }: { username: string }) {
const result = await queryUser({ id, provider: 'password_user' });
return result && result.password && result.password !== hashPassword('');
}
1 change: 1 addition & 0 deletions frontend/desktop/src/services/enable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const enableRecharge = () => {
return process.env.RECHARGE_ENABLED === 'true';
};
export const enableSignUp = () => process.env.SIGN_UP_ENABLED === 'true';
export const enableApi = () => process.env.API_ENABLED === 'true';
// costcenter
export const enableStripe = () =>
process.env['STRIPE_ENABLED'] === 'true' && !!process.env['STRIPE_PUB'];
Expand Down
13 changes: 13 additions & 0 deletions frontend/desktop/src/types/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ export type Session = {
// 帮忙导出用的
kubeconfig: KubeConfig;
};
export type ApiSession = {
// 提供一些简单的信息
user: {
readonly kubernetesUsername: string;
readonly name: string;
readonly avatar: string;
readonly nsID: string;
readonly nsUID: string;
readonly userID: string;
};
// 帮忙导出用的
kubeconfig: KubeConfig;
};
export type JWTPayload = {
kubeconfig: KubeConfig;
user: Record<'uid' | 'nsid' | 'k8s_username' | 'ns_uid', string>;
Expand Down
Loading

0 comments on commit 7c08451

Please sign in to comment.