[JavaScript, Программирование, Node.JS] Дино (Deno): Создать API для отдыха с помощью JWT (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
В преддверии старта курса "Node.JS Developer" приглашаем всех желающих посмотреть открытый урок на тему "Докеризация Node.js приложений".
А сейчас делимся традиционным переводом полезного материала. Приятного чтения.
Со времен первой версии Deno стал модным словом для разработчиков Javascript/TypeScript/Node. Давайте погрузимся в эту технологию, создав защищенный с помощью JWT REST API.Желательно уже иметь некоторые основы в Node и его экосистеме (Express, Nodemon, Sequelize и т.д.), чтобы следовать этому руководству.Что такое Дино (Deno)?Deno - это простая, современная и безопасная среда выполнения JavaScript и TypeScript, которая использует V8 и встроена в Rust.Уже есть много статей, детализирующих эту тему, поэтому я не буду останавливаться на ней. Я могу порекомендовать эту.ВступлениеС момента официального релиза V1, Deno стал "модным словечком" в течение нескольких недель (для фана, вот кривая популярности "deno" поиска в Google).
Что можно сделать с безопасной средой выполнения для Typescript and Javascript"?Чтобы лучше понять и высказать свое мнение об этом растущем проекте, я решил создать защищенный с помощью JWT REST API и поделиться с вами своими чувствами.Я привык работать с Node.js и Express.ЦельЦелью данного руководства будет создание защищенного REST API, что подразумевает:
- Настройка сервера
- Создание модели с ORM и базой данных
- CRUD-пользователь
- Реализация защищенной маршрутизации с JWT
ПредпосылкаДля создания нашего REST API с JWT, я буду использовать :
- Deno (рекомендую официальную документацию для установки: здесь)
- VSCode и плагин поддержки Deno, доступный по ссылке
А также следующие пакеты (я буду возвращаться к этому на протяжении всего урока):
УстановкаВо-первых, давайте настроим структуру проекта так, чтобы она содержала определенное руководство по созданию чистого и "готового к производству" проекта.
|-- DenoRestJwt
|-- controllers/
| |-- database/
| |-- models/
|-- helpers/
|-- middlewares/
|-- routers/
|-- app.ts
Если бы мы были на Node + Express приложении, я бы использовал Nodemon для облегчения разработки, Nodemon перезапускает сервер автоматически после изменений в коде.
Nodemon - это инструмент, который помогает разрабатывать приложения на основе node.js, автоматически перезапуская приложение Node при обнаружении изменений в файле в каталоге.
Чтобы сохранить тот же "комфорт разработки", я решил использовать Denon, его аналог для Deno.
deno install --allow-read --allow-run --allow-write -f --unstable
https://deno.land/x/denon/denon.ts
Давайте немного изменим конфигурацию Denon. Это будет полезно позже (особенно для управления переменными окружения).
// into denon.json
{
"$schema": "https://deno.land/x/denon/schema.json",
"env": {},
"scripts": {
"start": {
"cmd": "deno run app.ts"
}
}
}
Теперь мы готовы начать кодирование в хороших условиях! Чтобы запустить Denon, просто введите в консоле denon start:
➜ denon start
[denon] v2.0.2
[denon] watching path(s): *.*
[denon] watching extensions: ts,js,json
[denon] starting `deno run app.ts`
Compile file:///deno-crashtest/app.ts
[denon] clean exit - waiting for changes before restart
Вы видите, что наш сервер работает… но он ломается! Это нормально, у него нет кода для выполнения в app.ts.Давайте инициализируем наш серверЯ решил использовать фреймворк Oak.Oak - это промежуточный фреймворк для http-сервера Deno, включая промежуточное ПО маршрутизатора. Этот промежуточный фреймворк вдохновлен Koa, а промежуточный маршрутизатор вдохновлен @koa/router.Давайте инициализируем наш сервер с помощью Oak :
// app.ts
import { Application, Router, Status } from "https://deno.land/x/oak/mod.ts";
// Initialise app
const app = new Application();
// Initialise router
const router = new Router();
// Create first default route
router.get("/", (ctx) => {
ctx.response.status = Status.OK;
ctx.response.body = { message: "It's work !" };
});
app.use(router.routes());
app.use(router.allowedMethods());
console.log("? Deno start !");
await app.listen("0.0.0.0:3001");
Теперь, если мы запустим наш сервер с denon start.
error: Uncaught PermissionDenied: network access to "0.0.0.0:3001",
run again with the --allow-net flag
Это одно из больших различий между Deno и Node: Deno по умолчанию безопасен и не имеет доступа к network. Вы должны авторизовать его:
// into denon.json
"scripts": {
"start": {
// add --allow-net
"cmd": "deno run --allow-net app.ts"
}
}
Теперь вы можете получить доступ из браузера (хотя я советую использовать Postman) к localhost:3001 :
{
"message": "It's work !"
}
Установка базы данныхЯ буду использовать DenoDBв качестве ORM (в частности, потому что он поддерживает Sqlite3). Более того, он очень похож на Sequelize (к которому я привык).Давайте добавим первый контроллер Database и файл Sqlite3.
|-- DenoRestJwt
|-- controllers/
| |-- Database.ts
| |-- database/
| | |-- db.sqlite
| |-- models/
|-- app.ts
// Database.ts
import { Database } from "https://deno.land/x/denodb/mod.ts";
export class DatabaseController {
client: Database;
/**
* Initialise database client
*/
constructor() {
this.client = new Database("sqlite3", {
filepath: Deno.realPathSync("./controllers/database/db.sqlite"),
});
}
/**
* Initialise models
*/
async initModels() {
this.client.link([]);
await this.client.sync({});
}
}
Наш ORM инициализирован. Вы можете заметить, что я использую realPathSync, который требует дополнительного разрешения. Давайте добавим --allow-read недописанное и --allow-write недописанное в denon.json:
"scripts": {
"start": {
"cmd": "deno run --allow-write --allow-read --allow-net app.ts"
}
}
Все, что осталось сделать, это создать модель пользователя через наш ORM:
|-- DenoRestJwt
|-- controllers/
| |-- models/
| |-- User.ts
|-- app.ts
// User.ts
import { Model, DATA_TYPES } from "https://deno.land/x/denodb/mod.ts";
import nanoid from "https://deno.land/x/nanoid/mod.ts";
export interface IUser {
id?: string;
firstName: string;
lastName: string;
password: string;
}
export class User extends Model {
static table = "users";
static timestamps = true;
static fields = {
id: {
primaryKey: true,
type: DATA_TYPES.STRING,
},
firstName: {
type: DATA_TYPES.STRING,
},
lastName: {
type: DATA_TYPES.STRING,
},
password: {
type: DATA_TYPES.TEXT,
},
};
// Id will generate a nanoid by default
static defaults = {
id: nanoid(),
};
}
Здесь нет ничего нового, так что я не буду останавливаться на этом. (ps: Я использую nanoid для управления моим UUID, я позволю вам прочитать эту очень интересную статью об этом).Я пользуюсь этой возможностью, чтобы добавить функцию, которая будет полезна позже: хэш пароля. Для этого я использую Bcrypt:
// inside User's class
import * as bcrypt from "https://deno.land/x/bcrypt/mod.ts";
// ...
static async hashPassword(password: string) {
const salt = await bcrypt.genSalt(8);
return bcrypt.hash(password, salt);
}
И наконец, давайте свяжем нашу модель с нашим ORM :
// Database.ts
import { User } from "./models/User.ts";
export class DatabaseController {
//...
initModels() {
// Add User here
this.client.link([User]);
return this.client.sync({});
}
}
Хорошо! Теперь, когда наш сервер и база данных на месте, пришло время инициализировать маршруты создания аккаунтов… User controllerНет ничего более основного, чем хороший CRUD:
|-- DenoRestJwt
|-- controllers/
| |-- Database.ts
| |-- UserController.ts
import * as bcrypt from "https://deno.land/x/bcrypt/mod.ts";
import { IUser, User } from "./models/index.ts";
export class UserController {
async create(values: IUser) {
// Call static user method
const password = await User.hashPassword(values.password);
const user: IUser = {
firstName: values.firstName,
lastName: values.lastName,
password,
};
await User.create(user as any);
return values;
}
async delete(id: string) {
await User.deleteById(id);
}
getAll() {
return User.all();
}
getOne(id: string) {
return User.where("id", id).first();
}
async update(id: string, values: IUser) {
await User.where("id", id).update(values as any);
return this.getOne(id);
}
async login(lastName: string, password: string) {
const user = await User.where("lastName", lastName).first();
if (!user || !(await bcrypt.compare(password, user.password))) {
return false;
}
// TODO generate JWT
}
}
Я просто использую методы, предоставляемые ORM. Теперь нам осталось только управлять генерацией JWT.Настройка маршрутизацииТеперь пришло время создать наши различные пути и вызвать наш свежезакодированный контроллер.
|-- DenoRestJwt
|-- routers
|-- UserRoute.ts
import { Router, Status } from "https://deno.land/x/oak/mod.ts";
import { UserController } from "../controllers/UserController.ts";
import { BadRequest } from "../helpers/BadRequest.ts";
import { NotFound } from "../helpers/NotFound.ts";
// instantiate our controller
const controller = new UserController();
export function UserRoutes(router: Router) {
return router
.get("/users", async (ctx) => {
const users = await controller.getAll();
if (users) {
ctx.response.status = Status.OK;
ctx.response.body = users;
} else {
ctx.response.status = Status.NotFound;
ctx.response.body = [];
}
return;
})
.post("/login", async (ctx) => {
if (!ctx.request.hasBody) {
return BadRequest(ctx);
}
const { value } = await ctx.request.body();
// TODO generate JWT
ctx.response.status = Status.OK;
ctx.response.body = { jwt };
})
.get("/user/:id", async (ctx) => {
if (!ctx.params.id) {
return BadRequest(ctx);
}
const user = await controller.getOne(ctx.params.id);
if (user) {
ctx.response.status = Status.OK;
ctx.response.body = user;
return;
}
return NotFound(ctx);
})
.post("/user", async (ctx) => {
if (!ctx.request.hasBody) {
return BadRequest(ctx);
}
const { value } = await ctx.request.body();
const user = await controller.create(value);
if (user) {
ctx.response.status = Status.OK;
ctx.response.body = user;
return;
}
return NotFound(ctx);
})
.patch("/user/:id", async (ctx) => {
if (!ctx.request.hasBody || !ctx.params.id) {
return BadRequest(ctx);
}
const { value } = await ctx.request.body();
const user = await controller.update(ctx.params.id, value);
if (user) {
ctx.response.status = Status.OK;
ctx.response.body = user;
return;
}
return NotFound(ctx);
})
.delete("/user/:id", async (ctx) => {
if (!ctx.params.id) {
return BadRequest(ctx);
}
await controller.delete(ctx.params.id);
ctx.response.status = Status.OK;
ctx.response.body = { message: "Ok" };
});
}
Все, что нам нужно сделать, это вызвать нашу логику из нашего контроллера.Я использую методы HTTP для четкого разделения маршрутов. Я также создал хелперы управления возвращаемыми ошибками. Исходники можно найти прямо из проекта GitHub! Все, что нам нужно сделать, это вызвать наш маршрутизатор в нашем приложении:
// app.ts
import { DatabaseController } from "./controllers/Database.ts";
import { UserRoutes } from "./routers/UserRoute.ts";
const userRoutes = UserRoutes(router);
app.use(userRoutes.routes());
app.use(userRoutes.allowedMethods());
await new DatabaseController().initModels();
Безопасность и JWTПришло время добавить безопасности к этому проекту! Я использую JWTдля этого.
1. Создать защищенный маршрутПрежде всего, мы собираемся установить промежуточный слой:
- Проверяет, существует ли в запросе заголовок "Authorization".
- Достает заголовок
- Валидирует заголовок
- Возвращает ошибку / Принимает запрос и вызывает приватный маршрут
Я воспользуюсь библиотекой Djwt.
|-- DenoRestJwt
|-- middlewares/
| |-- jwt.ts
Наша функция должна будет принять в параметре контекст запроса, извлечь токен из заголовков, проверить его достоверность и действовать соответствующим образом.
import { Context, Status } from "https://deno.land/x/oak/mod.ts";
import { validateJwt } from "https://deno.land/x/djwt/validate.ts";
/**
* Create a default configuration
*/
export const JwtConfig = {
header: "Authorization",
schema: "Bearer",
// use Env variable
secretKey: Deno.env.get("SECRET") || "",
expirationTime: 60000,
type: "JWT",
alg: "HS256",
};
export async function jwtAuth(
ctx: Context<Record<string, any>>,
next: () => Promise<void>
) {
// Get the token from the request
const token = ctx.request.headers
.get(JwtConfig.header)
?.replace(`${JwtConfig.schema} `, "");
// reject request if token was not provide
if (!token) {
ctx.response.status = Status.Unauthorized;
ctx.response.body = { message: "Unauthorized" };
return;
}
// check the validity of the token
if (
!(await validateJwt(token, JwtConfig.secretKey, { isThrowing: false }))
) {
ctx.response.status = Status.Unauthorized;
ctx.response.body = { message: "Wrong Token" };
return;
}
// JWT is correct, so continue and call the private route
next();
}
Обратите внимание, что нам нужен секретный ключ, чтобы зашифровать наш токен. Для этого я использую переменные окружения Deno. Так что нам нужно внести несколько изменений в конфигурацию Denon: добавить нашу переменную и разрешить Deno получать переменные окружения.
{
"$schema": "<https://deno.land/x/denon/schema.json>",
// Add env variable
"env": {
"SECRET": "ADRIEN_IS_THE_BEST_AUTHOR_ON_MEDIUM"
},
"scripts": {
"start": {
// add the permission with --allow-env
"cmd": "deno run --allow-env --allow-read --allow-net app.ts"
}
}
}
(ps: если вы хотите обезопасить переменные окружения, я рекомендую этоучебное пособие)Тогда давайте создадим наш приватный маршрут.
|-- DenoRestJwt
|-- routers
|-- UserRoute.ts
|-- PrivateRoute.ts
Просто вызовите наш метод перед вызовом нашего маршрута:
import { Router, Status } from "https://deno.land/x/oak/mod.ts";
import { jwtAuth } from "../middlewares/jwt.ts";
export function PrivateRoutes(router: Router) {
// call our middleware before our private route
return router.get("/private", jwtAuth, async (ctx) => {
ctx.response.status = Status.OK;
ctx.response.body = { message: "Conntected !" };
});
}
Не забудьте добавить его в наше приложение:
import { Router, Status } from "https://deno.land/x/oak/mod.ts";
import { jwtAuth } from "../middlewares/jwt.ts";
export function PrivateRoutes(router: Router) {
// call our middleware before our private route
return router.get("/private", jwtAuth, async (ctx) => {
ctx.response.status = Status.OK;
ctx.response.body = { message: "Conntected !" };
});
}
Если мы попробуем вызвать наш API на /private , у нас будет корректный ответ:
{
"message": "Unauthorized"
}
2. JWT поколениеТеперь пришло время настроить генерацию токенов при входе пользователей в систему. Помните, что мы оставили // TODO generate JWT в нашем контроллере. Перед его завершением мы сначала добавим статический метод в нашу модель User, чтобы сгенерировать токен.
// User.ts
import {
makeJwt,
setExpiration,
Jose,
Payload,
} from "https://deno.land/x/djwt/create.ts";
import { JwtConfig } from "../../middlewares/jwt.ts";
// ...
export class User extends Model {
// ...
static generateJwt(id: string) {
// Create the payload with the expiration date (token have an expiry date) and the id of current user (you can add that you want)
const payload: Payload = {
id,
exp: setExpiration(new Date().getTime() + JwtConfig.expirationTime),
};
const header: Jose = {
alg: JwtConfig.alg as Jose["alg"],
typ: JwtConfig.type,
};
// return the generated token
return makeJwt({ header, payload, key: JwtConfig.secretKey });
}
// ...
}
Вызовем этот метод в нашем контроллере :
// UserController.ts
export class UserController {
// ...
async login(lastName: string, password: string) {
const user = await User.where("lastName", lastName).first();
if (!user || !(await bcrypt.compare(password, user.password))) {
return false;
}
// Call our new static method
return User.generateJwt(user.id);
}
}
Наконец, давайте добавим эту логику в наш маршрутизатор:
// UserRoute.ts
// ...
.post("/login", async (ctx) => {
if (!ctx.request.hasBody) {
return BadRequest(ctx);
}
const { value } = await ctx.request.body();
// generate jwt
const jwt = await controller.login(value.lastName, value.password);
if (!jwt) {
return BadRequest(ctx);
}
ctx.response.status = Status.OK;
// and return it
ctx.response.body = { jwt };
})
// ...
Теперь, если мы попытаемся подключиться, у нас есть :
// localhost:3001/login
{
"jwt":
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IlEyY0ZZcUxKWk5Hc0toN0FWV0hzUiIsImV4cCI6MTU5MDg0NDU2MDM5MH0.drQ3ay5_DYuXEOnH2Z0RKbhq9nZElWCMvmypjI4BjIk"
}
(Не забудьте создать аккаунт раньше)Давайте добавим этот токен в наши заголовки Authorization и снова вызовем наш приватный маршрут:
// localhost:3001/private with token in headers
{
"message": "Connected !"
}
Здорово! Есть наш защищенный API ?.Вы можете найти этот проект на моем Github: здесь(я добавляю коллекцию почтальона, чтобы сделать запрос).Мои впечатления о DenoЯ решил поделиться с вами своими впечатлениями относительно Depo, что вам даст некое представление о нем:Импорт модулей по URL в начале немного контр-интуитивно понятен: всегда хочется сделать npm i или yarn add. Более того, нам приходится запускать Deno, чтобы кэшировать наши импорты, и только после этого мы имеем доступ к автозавершению.
The remote module XXX has not been cached
- Я всегда использую TypeScript в своих проектах на Javascript, так что в начале я совсем не потерялся. Напротив, я довольно хорошо знаком с ним.
- Интересный момент: permissions. Я думаю, хорошо, что Deno, например, требует permissions на доступ к сети. Это заставляет нас, как разработчиков, быть в курсе доступа и прав нашей программы. (более безопасно)
- Сначала мы немного запутались в том, где искать пакеты (https://deno.land/x → 460 пакетов и NPM → + 1 миллион).
- Вы никогда не можете быть уверены, что пакет также работает на Deno или нет. Вы +всегда хотите быть ближе к тому, что знаете и используете на Node, чтобы перенести его на Deno. Я не знаю, хорошо это или плохо, это всё ещё javascript…
Узнать подробнее о курсе "Node.JS Developer".
Посмотреть открытый урок на тему "Докеризация Node.js приложений".
ЗАБРАТЬ СКИДКУ
===========
Источник:
habr.com
===========
===========
Автор оригинала: Adrien Figueiredo
===========Похожие новости:
- [Разработка игр] Баланс в настольном геймдизайне: строим графы с помощью Google App Script и Gephi
- [Программирование, Git, Dart, Microsoft Azure] Когда твой код стал общим: история от дебюта до эндшпиля
- [JavaScript, ReactJS] Эпическая сага про маленький custom hook для React (генераторы, sagas, rxjs) часть 2
- [JavaScript, Программирование, TypeScript] Кастомизация компонентов Ant Design и оптимизация бандла
- [Python, JavaScript, Браузеры] Brython: заменяем JavaScript на Python на фронтенде (перевод)
- [JavaScript, ReactJS] Эпическая сага про маленький custom hook для React (генераторы, sagas, rxjs)
- [JavaScript, Node.JS, TypeScript] Оптимизация трафика при синхронизация состояний через Jsonpatch
- [Программирование, Управление персоналом, Карьера в IT-индустрии] Почему сеньоры ненавидят собеседования с кодингом, и что компании должны использовать вместо них (перевод)
- [Информационная безопасность, Google Chrome, Расширения для браузеров, Браузеры] В бета-версии Chrome 88 включили Manifest V3, который изменит доступ расширений к данным
- [Программирование, Мозг, Здоровье] Зачем мне психотерапевт?
Теги для поиска: #_javascript, #_programmirovanie (Программирование), #_node.js, #_deno, #_api, #_typescript, #_nodejs, #_javascript, #_blog_kompanii_otus._onlajnobrazovanie (
Блог компании OTUS. Онлайн-образование
), #_javascript, #_programmirovanie (
Программирование
), #_node.js
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 02:20
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
В преддверии старта курса "Node.JS Developer" приглашаем всех желающих посмотреть открытый урок на тему "Докеризация Node.js приложений".
А сейчас делимся традиционным переводом полезного материала. Приятного чтения. Что можно сделать с безопасной средой выполнения для Typescript and Javascript"?Чтобы лучше понять и высказать свое мнение об этом растущем проекте, я решил создать защищенный с помощью JWT REST API и поделиться с вами своими чувствами.Я привык работать с Node.js и Express.ЦельЦелью данного руководства будет создание защищенного REST API, что подразумевает:
|-- DenoRestJwt
|-- controllers/ | |-- database/ | |-- models/ |-- helpers/ |-- middlewares/ |-- routers/ |-- app.ts Nodemon - это инструмент, который помогает разрабатывать приложения на основе node.js, автоматически перезапуская приложение Node при обнаружении изменений в файле в каталоге.
deno install --allow-read --allow-run --allow-write -f --unstable
https://deno.land/x/denon/denon.ts // into denon.json
{ "$schema": "https://deno.land/x/denon/schema.json", "env": {}, "scripts": { "start": { "cmd": "deno run app.ts" } } } ➜ denon start
[denon] v2.0.2 [denon] watching path(s): *.* [denon] watching extensions: ts,js,json [denon] starting `deno run app.ts` Compile file:///deno-crashtest/app.ts [denon] clean exit - waiting for changes before restart // app.ts
import { Application, Router, Status } from "https://deno.land/x/oak/mod.ts"; // Initialise app const app = new Application(); // Initialise router const router = new Router(); // Create first default route router.get("/", (ctx) => { ctx.response.status = Status.OK; ctx.response.body = { message: "It's work !" }; }); app.use(router.routes()); app.use(router.allowedMethods()); console.log("? Deno start !"); await app.listen("0.0.0.0:3001"); error: Uncaught PermissionDenied: network access to "0.0.0.0:3001",
run again with the --allow-net flag // into denon.json
"scripts": { "start": { // add --allow-net "cmd": "deno run --allow-net app.ts" } } {
"message": "It's work !" } |-- DenoRestJwt
|-- controllers/ | |-- Database.ts | |-- database/ | | |-- db.sqlite | |-- models/ |-- app.ts // Database.ts
import { Database } from "https://deno.land/x/denodb/mod.ts"; export class DatabaseController { client: Database; /** * Initialise database client */ constructor() { this.client = new Database("sqlite3", { filepath: Deno.realPathSync("./controllers/database/db.sqlite"), }); } /** * Initialise models */ async initModels() { this.client.link([]); await this.client.sync({}); } } "scripts": {
"start": { "cmd": "deno run --allow-write --allow-read --allow-net app.ts" } } |-- DenoRestJwt
|-- controllers/ | |-- models/ | |-- User.ts |-- app.ts // User.ts
import { Model, DATA_TYPES } from "https://deno.land/x/denodb/mod.ts"; import nanoid from "https://deno.land/x/nanoid/mod.ts"; export interface IUser { id?: string; firstName: string; lastName: string; password: string; } export class User extends Model { static table = "users"; static timestamps = true; static fields = { id: { primaryKey: true, type: DATA_TYPES.STRING, }, firstName: { type: DATA_TYPES.STRING, }, lastName: { type: DATA_TYPES.STRING, }, password: { type: DATA_TYPES.TEXT, }, }; // Id will generate a nanoid by default static defaults = { id: nanoid(), }; } // inside User's class
import * as bcrypt from "https://deno.land/x/bcrypt/mod.ts"; // ... static async hashPassword(password: string) { const salt = await bcrypt.genSalt(8); return bcrypt.hash(password, salt); } // Database.ts
import { User } from "./models/User.ts"; export class DatabaseController { //... initModels() { // Add User here this.client.link([User]); return this.client.sync({}); } } |-- DenoRestJwt
|-- controllers/ | |-- Database.ts | |-- UserController.ts import * as bcrypt from "https://deno.land/x/bcrypt/mod.ts";
import { IUser, User } from "./models/index.ts"; export class UserController { async create(values: IUser) { // Call static user method const password = await User.hashPassword(values.password); const user: IUser = { firstName: values.firstName, lastName: values.lastName, password, }; await User.create(user as any); return values; } async delete(id: string) { await User.deleteById(id); } getAll() { return User.all(); } getOne(id: string) { return User.where("id", id).first(); } async update(id: string, values: IUser) { await User.where("id", id).update(values as any); return this.getOne(id); } async login(lastName: string, password: string) { const user = await User.where("lastName", lastName).first(); if (!user || !(await bcrypt.compare(password, user.password))) { return false; } // TODO generate JWT } } |-- DenoRestJwt
|-- routers |-- UserRoute.ts import { Router, Status } from "https://deno.land/x/oak/mod.ts";
import { UserController } from "../controllers/UserController.ts"; import { BadRequest } from "../helpers/BadRequest.ts"; import { NotFound } from "../helpers/NotFound.ts"; // instantiate our controller const controller = new UserController(); export function UserRoutes(router: Router) { return router .get("/users", async (ctx) => { const users = await controller.getAll(); if (users) { ctx.response.status = Status.OK; ctx.response.body = users; } else { ctx.response.status = Status.NotFound; ctx.response.body = []; } return; }) .post("/login", async (ctx) => { if (!ctx.request.hasBody) { return BadRequest(ctx); } const { value } = await ctx.request.body(); // TODO generate JWT ctx.response.status = Status.OK; ctx.response.body = { jwt }; }) .get("/user/:id", async (ctx) => { if (!ctx.params.id) { return BadRequest(ctx); } const user = await controller.getOne(ctx.params.id); if (user) { ctx.response.status = Status.OK; ctx.response.body = user; return; } return NotFound(ctx); }) .post("/user", async (ctx) => { if (!ctx.request.hasBody) { return BadRequest(ctx); } const { value } = await ctx.request.body(); const user = await controller.create(value); if (user) { ctx.response.status = Status.OK; ctx.response.body = user; return; } return NotFound(ctx); }) .patch("/user/:id", async (ctx) => { if (!ctx.request.hasBody || !ctx.params.id) { return BadRequest(ctx); } const { value } = await ctx.request.body(); const user = await controller.update(ctx.params.id, value); if (user) { ctx.response.status = Status.OK; ctx.response.body = user; return; } return NotFound(ctx); }) .delete("/user/:id", async (ctx) => { if (!ctx.params.id) { return BadRequest(ctx); } await controller.delete(ctx.params.id); ctx.response.status = Status.OK; ctx.response.body = { message: "Ok" }; }); } // app.ts
import { DatabaseController } from "./controllers/Database.ts"; import { UserRoutes } from "./routers/UserRoute.ts"; const userRoutes = UserRoutes(router); app.use(userRoutes.routes()); app.use(userRoutes.allowedMethods()); await new DatabaseController().initModels(); 1. Создать защищенный маршрутПрежде всего, мы собираемся установить промежуточный слой:
|-- DenoRestJwt
|-- middlewares/ | |-- jwt.ts import { Context, Status } from "https://deno.land/x/oak/mod.ts";
import { validateJwt } from "https://deno.land/x/djwt/validate.ts"; /** * Create a default configuration */ export const JwtConfig = { header: "Authorization", schema: "Bearer", // use Env variable secretKey: Deno.env.get("SECRET") || "", expirationTime: 60000, type: "JWT", alg: "HS256", }; export async function jwtAuth( ctx: Context<Record<string, any>>, next: () => Promise<void> ) { // Get the token from the request const token = ctx.request.headers .get(JwtConfig.header) ?.replace(`${JwtConfig.schema} `, ""); // reject request if token was not provide if (!token) { ctx.response.status = Status.Unauthorized; ctx.response.body = { message: "Unauthorized" }; return; } // check the validity of the token if ( !(await validateJwt(token, JwtConfig.secretKey, { isThrowing: false })) ) { ctx.response.status = Status.Unauthorized; ctx.response.body = { message: "Wrong Token" }; return; } // JWT is correct, so continue and call the private route next(); } {
"$schema": "<https://deno.land/x/denon/schema.json>", // Add env variable "env": { "SECRET": "ADRIEN_IS_THE_BEST_AUTHOR_ON_MEDIUM" }, "scripts": { "start": { // add the permission with --allow-env "cmd": "deno run --allow-env --allow-read --allow-net app.ts" } } } |-- DenoRestJwt
|-- routers |-- UserRoute.ts |-- PrivateRoute.ts import { Router, Status } from "https://deno.land/x/oak/mod.ts";
import { jwtAuth } from "../middlewares/jwt.ts"; export function PrivateRoutes(router: Router) { // call our middleware before our private route return router.get("/private", jwtAuth, async (ctx) => { ctx.response.status = Status.OK; ctx.response.body = { message: "Conntected !" }; }); } import { Router, Status } from "https://deno.land/x/oak/mod.ts";
import { jwtAuth } from "../middlewares/jwt.ts"; export function PrivateRoutes(router: Router) { // call our middleware before our private route return router.get("/private", jwtAuth, async (ctx) => { ctx.response.status = Status.OK; ctx.response.body = { message: "Conntected !" }; }); } {
"message": "Unauthorized" } // User.ts
import { makeJwt, setExpiration, Jose, Payload, } from "https://deno.land/x/djwt/create.ts"; import { JwtConfig } from "../../middlewares/jwt.ts"; // ... export class User extends Model { // ... static generateJwt(id: string) { // Create the payload with the expiration date (token have an expiry date) and the id of current user (you can add that you want) const payload: Payload = { id, exp: setExpiration(new Date().getTime() + JwtConfig.expirationTime), }; const header: Jose = { alg: JwtConfig.alg as Jose["alg"], typ: JwtConfig.type, }; // return the generated token return makeJwt({ header, payload, key: JwtConfig.secretKey }); } // ... } // UserController.ts
export class UserController { // ... async login(lastName: string, password: string) { const user = await User.where("lastName", lastName).first(); if (!user || !(await bcrypt.compare(password, user.password))) { return false; } // Call our new static method return User.generateJwt(user.id); } } // UserRoute.ts
// ... .post("/login", async (ctx) => { if (!ctx.request.hasBody) { return BadRequest(ctx); } const { value } = await ctx.request.body(); // generate jwt const jwt = await controller.login(value.lastName, value.password); if (!jwt) { return BadRequest(ctx); } ctx.response.status = Status.OK; // and return it ctx.response.body = { jwt }; }) // ... // localhost:3001/login
{ "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IlEyY0ZZcUxKWk5Hc0toN0FWV0hzUiIsImV4cCI6MTU5MDg0NDU2MDM5MH0.drQ3ay5_DYuXEOnH2Z0RKbhq9nZElWCMvmypjI4BjIk" } // localhost:3001/private with token in headers
{ "message": "Connected !" } The remote module XXX has not been cached
Узнать подробнее о курсе "Node.JS Developer".
Посмотреть открытый урок на тему "Докеризация Node.js приложений". ЗАБРАТЬ СКИДКУ =========== Источник: habr.com =========== =========== Автор оригинала: Adrien Figueiredo ===========Похожие новости:
Блог компании OTUS. Онлайн-образование ), #_javascript, #_programmirovanie ( Программирование ), #_node.js |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 02:20
Часовой пояс: UTC + 5