feat: account

This commit is contained in:
Kirill 2022-12-20 15:07:06 +00:00 committed by Mikhail
parent 0e4a34bb6d
commit ded850436c
23 changed files with 240 additions and 45 deletions

@ -2,6 +2,10 @@
HTTP_HOST=localhost
HTTP_PORT=8080
# Auth service
AUTH_SERVICE_HOST=localhost
AUTH_SERVICE_PORT=8081
# Database Options
DB_HOST=127.0.0.1
DB_PORT=27017

@ -130,7 +130,7 @@
"error",
{
"props": true,
"ignorePropertyModificationsFor": ["accamulator"]
"ignorePropertyModificationsFor": ["accamulator", "request"]
}
],
"object-shorthand": "off",

@ -28,6 +28,13 @@ DB_PASSWORD - mongo password
DB_NAME - database name
```
**Для подключения к сервису авторизации**
```
AUTH_SERVICE_HOST - auth service host
AUTH_SERVICE_PORT - auth service port
```
## Среды окружения
```

@ -23,6 +23,7 @@
"@fastify/cookie": "^8.3.0",
"@fastify/cors": "^8.2.0",
"@fastify/jwt": "^6.3.3",
"axios": "^1.2.1",
"bcryptjs": "^2.4.3",
"dotenv": "^16.0.3",
"fastify": "^4.9.2",

16
src/clients/auth/index.ts Normal file

@ -0,0 +1,16 @@
import { authService } from "./instance";
import type { User } from "@/types/models/user.type";
import type { GetUserRequest } from "./types";
export const getUser = async (request: GetUserRequest): Promise<User | null> => {
try {
const { data } = await authService.get<User>(`/user/${request.id}`);
return data;
} catch (nativeError) {
console.error(nativeError);
}
return null;
};

@ -0,0 +1,7 @@
import axios from "axios";
import { CONFIGURATION } from "@/constants/configuration";
export const authService = axios.create({
baseURL: `${CONFIGURATION.authService.host}:${CONFIGURATION.authService.port}`,
});

@ -0,0 +1,3 @@
export type GetUserRequest = {
id: string;
};

@ -11,10 +11,11 @@ export const CONFIGURATION = {
database: process.env.DB_NAME || "database",
},
service: {
privateAccessSecretKey: process.env.PRIVATE_ACCESS_SECRET_KEY || "",
publicAccessSecretKey: process.env.PUBLIC_ACCESS_SECRET_KEY || "",
privateRefreshSecretKey: process.env.PRIVATE_REFRESH_SECRET_KEY || "",
publicRefreshSecretKey: process.env.PUBLIC_REFRESH_SECRET_KEY || "",
salt: Number(process.env.SALT) || 10,
},
authService: {
host: process.env.AUTH_SERVICE_HOST || "",
port: Number(process.env.AUTH_SERVICE_PORT) || 8081,
},
} as const;

@ -0,0 +1,52 @@
import { Types } from "mongoose";
import { AccountModel } from "@/models/account.model";
import { getUser } from "@/clients/auth";
import { validateEmptyFields } from "@/utils/validate-empty-fields";
import type { FastifyReply } from "fastify";
import type { CreateAccountRequest, GetAccountRequest } from "./types";
export const createAccount = async (request: CreateAccountRequest, reply: FastifyReply) => {
if (!Types.ObjectId.isValid(request.user.id)) {
reply.status(400);
return new Error("invalid user id");
}
const account = await AccountModel.findById(request.user.id).lean();
if (account) {
reply.status(409);
return new Error("account already exist");
}
const user = await getUser({ id: request.user.id });
if (!user) {
reply.status(404);
return new Error("user not found");
}
const createdAccount = new AccountModel({
userId: user._id,
});
return createdAccount.save();
};
export const getAccount = async (request: GetAccountRequest, reply: FastifyReply) => {
const [getAccountRequestBody, error] = validateEmptyFields(request.params || {}, ["userId"]);
if (!getAccountRequestBody || error) {
reply.status(400);
return error;
}
if (!Types.ObjectId.isValid(getAccountRequestBody.userId)) {
reply.status(400);
return new Error("invalid user id");
}
return AccountModel.findById(getAccountRequestBody.userId).lean();
};

@ -0,0 +1,19 @@
import type { FastifyRequest, FastifyReply, HookHandlerDoneFunction as Done } from "fastify";
export const verifyUser = async (request: FastifyRequest, reply: FastifyReply, done: Done) => {
try {
const { id } = await request.jwtVerify<{ id?: string }>({ onlyCookie: true });
if (!id) {
reply.status(401);
return reply.send("user id is empty");
}
request.user = { id };
} catch (nativeError) {
reply.status(401);
return reply.send(nativeError);
}
done();
};

@ -0,0 +1,6 @@
import type { FastifyRequest } from "fastify";
import type { CreateAccountRoute, GetAccountRoute } from "@/types/routes/account-routes.type";
export type CreateAccountRequest = FastifyRequest<CreateAccountRoute>;
export type GetAccountRequest = FastifyRequest<GetAccountRoute>;

@ -15,7 +15,11 @@ const server = new Server({
jwt: {
secret: {
public: CONFIGURATION.service.publicAccessSecretKey,
private: CONFIGURATION.service.privateAccessSecretKey,
private: "",
},
verify: {
allowedIss: "pena-auth-service",
allowedAud: "pena",
},
},
},

@ -2,24 +2,17 @@ import { Schema, model, SchemaDefinition } from "mongoose";
import { eloquentModelSchema } from "./eloquent-model.schema";
import type { User } from "@/types/models/user.type";
import type { Account } from "@/types/models/account.type";
const schema: SchemaDefinition<User> = {
login: {
const schema: SchemaDefinition<Account> = {
userId: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
phoneNumber: {
nickname: {
type: String,
required: true,
default: "nickname",
},
avatar: {
type: String,
@ -34,9 +27,9 @@ const schema: SchemaDefinition<User> = {
const schemaSettings = {
versionKey: false,
collection: "users",
collection: "accounts",
};
const UserSchema = new Schema<User>(schema, schemaSettings);
const AccountSchema = new Schema<Account>(schema, schemaSettings);
export const UserModel = model("User", UserSchema);
export const AccountModel = model("Account", AccountSchema);

@ -1,5 +1,5 @@
import type { SchemaDefinition } from "mongoose";
import type { EloquentModel } from "@/types/models/eloquent-model";
import type { EloquentModel } from "@/types/models/eloquent-model.type";
export const eloquentModelSchema: SchemaDefinition<EloquentModel> = {
createdAt: {

@ -1,23 +0,0 @@
import { Schema, model, SchemaDefinition } from "mongoose";
import type { Token } from "@/types/models/token.type";
const schema: SchemaDefinition<Token> = {
userId: {
type: String,
required: true,
},
refreshToken: {
type: String,
required: true,
},
};
const schemaSettings = {
versionKey: false,
collection: "tokens",
};
const TokenSchema = new Schema<Token>(schema, schemaSettings);
export const TokenModel = model("Token", TokenSchema);

@ -0,0 +1,12 @@
import { createAccount, getAccount } from "@/handlers/account";
import { verifyUser } from "@/handlers/account/middleware";
import type { FastifyInstance, FastifyPluginOptions } from "fastify";
import type { GetAccountRoute, CreateAccountRoute } from "@/types/routes/account-routes.type";
export const setRoleRoutes = <T = FastifyPluginOptions>(server: FastifyInstance, opts: T, done: () => void): void => {
server.get<GetAccountRoute>("/:userId", getAccount);
server.post<CreateAccountRoute>("/", { preHandler: [verifyUser] }, createAccount);
done();
};

8
src/types/fastify-jwt.d.ts vendored Normal file

@ -0,0 +1,8 @@
import "@fastify/jwt";
declare module "@fastify/jwt" {
interface FastifyJWT {
payload: { id: string };
user: { id: string };
}
}

@ -0,0 +1,7 @@
export type Account = {
userId: string;
nickname: string;
avatar: string;
role: string;
privilegies: Record<string, string>;
};

@ -1,4 +1,4 @@
import type { EloquentModel } from "./eloquent-model";
import type { EloquentModel } from "./eloquent-model.type";
export type Role = EloquentModel & {
name: string;

@ -0,0 +1,8 @@
import type { EloquentModel } from "./eloquent-model.type";
export type User = EloquentModel & {
_id: string;
login: string;
email: string;
phoneNumber: string;
};

@ -0,0 +1,13 @@
import type { RequestGenericInterface } from "fastify";
export type GetAccountRoute = RequestGenericInterface & {
Params?: {
userId?: string;
};
};
export type CreateAccountRoute = RequestGenericInterface & {
Params?: {
userId?: string;
};
};

@ -2049,6 +2049,11 @@ async-mutex@^0.3.2:
dependencies:
tslib "^2.3.1"
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
atomic-sleep@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b"
@ -2063,6 +2068,15 @@ avvio@^8.2.0:
debug "^4.0.0"
fastq "^1.6.1"
axios@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.2.1.tgz#44cf04a3c9f0c2252ebd85975361c026cb9f864a"
integrity sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==
dependencies:
follow-redirects "^1.15.0"
form-data "^4.0.0"
proxy-from-env "^1.1.0"
babel-jest@^29.3.1:
version "29.3.1"
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.3.1.tgz#05c83e0d128cd48c453eea851482a38782249f44"
@ -2366,6 +2380,13 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
commander@^9.0.0:
version "9.4.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.1.tgz#d1dd8f2ce6faf93147295c0df13c7c21141cfbdd"
@ -2530,6 +2551,11 @@ define-properties@^1.1.3, define-properties@^1.1.4:
has-property-descriptors "^1.0.0"
object-keys "^1.1.1"
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
denque@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1"
@ -3134,6 +3160,20 @@ flatted@^3.1.0:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787"
integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
follow-redirects@^1.15.0:
version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
forwarded@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
@ -4357,6 +4397,18 @@ micromatch@^4.0.4:
braces "^3.0.2"
picomatch "^2.3.1"
mime-db@1.52.0:
version "1.52.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-types@^2.1.12:
version "2.1.35"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
mimic-fn@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
@ -4843,6 +4895,11 @@ proxy-addr@^2.0.7:
forwarded "0.2.0"
ipaddr.js "1.9.1"
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
pstree.remy@^1.1.8:
version "1.1.8"
resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a"