From 4d25fea126785171c1fe894779d7a91cb18422c8 Mon Sep 17 00:00:00 2001 From: Gaurav Misra Date: Fri, 8 Oct 2021 11:38:01 -0700 Subject: [PATCH 1/6] Make starter work with Deno 1.0 - Use importmap.json to correctly import unversioned imports in std (this was breaking due to 'v' prefix) - Upgrade dotenv to 0.4.3 to fix a dep error - Upgrade mysql to 2.6.1 to fix a dep error - Upgrade bcrypt to 0.2.3 to fix a dep error - Upgrade djwt to 1.0 to fix a dep error - Migrate usage of validateJwt to 1.0 - Update README --- Dockerfile | 4 ++-- README.md | 6 ++++-- config/config.ts | 2 +- db/db.ts | 2 +- helpers/encription.ts | 2 +- helpers/jwt.ts | 14 ++++++++------ import_map.json | 8 ++++++++ middlewares/jwt-auth.middleware.ts | 7 ++++--- 8 files changed, 29 insertions(+), 16 deletions(-) create mode 100644 import_map.json diff --git a/Dockerfile b/Dockerfile index c6e3930..bf62cdc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,5 +7,5 @@ WORKDIR /usr/src/app COPY . . USER deno -RUN deno cache app.ts -CMD ["run", "--allow-read", "--allow-net", "--unstable", "app.ts"] \ No newline at end of file +RUN deno cache --unstable --importmap import_map.json app.ts +CMD ["run", "--allow-read", "--allow-net", "--unstable", "--importmap import_map.json", "app.ts"] \ No newline at end of file diff --git a/README.md b/README.md index f6b7b4f..186c57c 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ This is a starter project to create Deno RESTful API using oak. [oak](https://github.com/oakserver/oak) is a middleware framework and router middleware for Deno, inspired by popular Node.js framework [Koa](https://koajs.com/) and [@koa/router](https://github.com/koajs/router/). +Note: Only Deno 1.0 is supported at the moment (work is ongoing to support newer versions). + This project covers - Swagger Open API doc - Docker container environment @@ -50,7 +52,7 @@ We can run the project **with/ without Docker**. - For non-docker run API server with Deno run time ``` - $ deno run --allow-read --allow-net app.ts + $ deno run --allow-read --allow-net --unstable --importmap import_map.json app.ts ``` - **API** - Browse `API` at [http://localhost:8000](http://localhost:8000) @@ -82,7 +84,7 @@ deno run --allow-net --allow-read --allow-write https://deno.land/x/nessie@v1.0. |[mysql@2.2.0](https://deno.land/x/mysql@2.2.0)|MySQL driver for Deno| |[nessie@v1.0.0-rc3](https://deno.land/x/nessie@v1.0.0-rc3)| DB migration tool for Deno| |[validasaur@v0.7.0](https://deno.land/x/validasaur@v0.7.0)| validation library| -|[djwt@v0.9.0](https://deno.land/x/djwt@v0.9.0)| JWT token encoding| +|[djwt@v1.0](https://deno.land/x/djwt@v1.0)| JWT token encoding| |[bcrypt@v0.2.1](https://deno.land/x/bcrypt@v0.2.1)| bcrypt encription lib| ### Project Layout diff --git a/config/config.ts b/config/config.ts index d71ac8e..0a6e94b 100644 --- a/config/config.ts +++ b/config/config.ts @@ -1,2 +1,2 @@ -import { config as loadConfig } from "https://deno.land/x/dotenv@v0.4.2/mod.ts"; +import { config as loadConfig } from "https://deno.land/x/dotenv@v0.4.3/mod.ts"; export const config = loadConfig(); diff --git a/db/db.ts b/db/db.ts index aa4ff55..2112f64 100644 --- a/db/db.ts +++ b/db/db.ts @@ -1,4 +1,4 @@ -import { Client } from "https://deno.land/x/mysql@2.2.0/mod.ts"; +import { Client } from "https://deno.land/x/mysql@v2.6.1/mod.ts"; import { config } from "./../config/config.ts"; const port = config.DB_PORT ? parseInt(config.DB_PORT || "") : undefined; diff --git a/helpers/encription.ts b/helpers/encription.ts index 633bd1c..6215cd2 100644 --- a/helpers/encription.ts +++ b/helpers/encription.ts @@ -1,4 +1,4 @@ -import * as bcrypt from "https://deno.land/x/bcrypt@v0.2.1/mod.ts"; +import * as bcrypt from "https://deno.land/x/bcrypt@v0.2.3/mod.ts"; /** * encript given string */ diff --git a/helpers/jwt.ts b/helpers/jwt.ts index 3c6d6e4..b4408e5 100644 --- a/helpers/jwt.ts +++ b/helpers/jwt.ts @@ -3,8 +3,8 @@ import { Payload, makeJwt, setExpiration, -} from "https://deno.land/x/djwt@v0.9.0/create.ts"; -import { validateJwt } from "https://deno.land/x/djwt@v0.9.0/validate.ts"; +} from "https://deno.land/x/djwt@v1.0/create.ts"; +import { validateJwt } from "https://deno.land/x/djwt@v1.0/validate.ts"; import { config } from "./../config/config.ts"; const { @@ -13,8 +13,10 @@ const { JWT_REFRESH_TOKEN_EXP, } = config; +const JWTAlgorithm = "HS256"; + const header: Jose = { - alg: "HS256", + alg: JWTAlgorithm, typ: "JWT", }; @@ -43,12 +45,12 @@ const getRefreshToken = (user: any) => { const getJwtPayload = async (token: string): Promise => { try { - const jwtObject = await validateJwt(token, JWT_TOKEN_SECRET); - if (jwtObject && jwtObject.payload) { + const jwtObject = await validateJwt({jwt: token, key: JWT_TOKEN_SECRET, algorithm: [JWTAlgorithm], critHandlers: {}}); + if (jwtObject.isValid) { return jwtObject.payload; } } catch (err) {} return null; }; -export { getAuthToken, getRefreshToken, getJwtPayload }; +export { getAuthToken, getRefreshToken, getJwtPayload, JWTAlgorithm }; diff --git a/import_map.json b/import_map.json new file mode 100644 index 0000000..09d8231 --- /dev/null +++ b/import_map.json @@ -0,0 +1,8 @@ +{ + "imports": { + "https://deno.land/std@v0.61.0/encoding/hex.ts": "https://deno.land/std@0.61.0/encoding/hex.ts", + "https://deno.land/std@v0.61.0/encoding/base64url.ts": "https://deno.land/std@0.61.0/encoding/base64url.ts", + "https://deno.land/std@v0.61.0/hash/sha256.ts": "https://deno.land/std@0.61.0/hash/sha256.ts", + "https://deno.land/std@v0.61.0/hash/sha512.ts": "https://deno.land/std@0.61.0/hash/sha512.ts" + } +} \ No newline at end of file diff --git a/middlewares/jwt-auth.middleware.ts b/middlewares/jwt-auth.middleware.ts index 85ba541..2cd3cad 100644 --- a/middlewares/jwt-auth.middleware.ts +++ b/middlewares/jwt-auth.middleware.ts @@ -1,5 +1,6 @@ import { Context, AuthUser } from "./../types.ts"; -import { validateJwt } from "https://deno.land/x/djwt@v0.9.0/validate.ts"; +import { validateJwt } from "https://deno.land/x/djwt@v1.0/validate.ts"; +import { JWTAlgorithm } from "./../helpers/jwt.ts"; /** * Decode token and returns payload @@ -8,8 +9,8 @@ import { validateJwt } from "https://deno.land/x/djwt@v0.9.0/validate.ts"; */ const getJwtPayload = async (token: string, secret: string): Promise => { try { - const jwtObject = await validateJwt(token, secret); - if (jwtObject && jwtObject.payload) { + const jwtObject = await validateJwt({jwt: token, key: secret, algorithm: [JWTAlgorithm], critHandlers: {}}); + if (jwtObject.isValid) { return jwtObject.payload; } } catch (err) {} From 67e43cb7058cb62fa77dcfce5fe6578c2d7f0c92 Mon Sep 17 00:00:00 2001 From: Gaurav Misra Date: Fri, 8 Oct 2021 12:45:56 -0700 Subject: [PATCH 2/6] Fixed code to conform to Deno 1.5.0 - Imported types with `import type` - Upgraded to validasaur@v0.15.0 to fix `import type` errors --- app.ts | 2 +- helpers/roles.ts | 2 +- import_map.json | 4 +++- middlewares/error.middleware.ts | 2 +- middlewares/jwt-auth.middleware.ts | 2 +- middlewares/logger.middleware.ts | 2 +- middlewares/request-id.middleware.ts | 2 +- middlewares/request-validator.middleware.ts | 4 ++-- middlewares/timing.middleware.ts | 2 +- middlewares/user-guard.middleware.ts | 2 +- repositories/user.repository.ts | 2 +- routes/auth.routes.ts | 2 +- routes/routes.ts | 2 +- routes/user.routes.ts | 4 ++-- 14 files changed, 18 insertions(+), 16 deletions(-) diff --git a/app.ts b/app.ts index 05b7337..0f5259d 100644 --- a/app.ts +++ b/app.ts @@ -2,7 +2,7 @@ import { Application } from "https://deno.land/x/oak@v5.0.0/mod.ts"; import * as middlewares from "./middlewares/middlewares.ts"; import { oakCors } from "https://deno.land/x/cors/mod.ts"; import { router } from "./routes/routes.ts"; -import { Context } from "./types.ts"; +import type { Context } from "./types.ts"; import { config } from "./config/config.ts"; const port = 8000; diff --git a/helpers/roles.ts b/helpers/roles.ts index f61891d..36bb7b8 100644 --- a/helpers/roles.ts +++ b/helpers/roles.ts @@ -1,4 +1,4 @@ -import { AuthUser } from "../types.ts"; +import type { AuthUser } from "../types.ts"; import { UserRole } from "../types/user/user-role.ts"; const hasUserRole = (user: AuthUser, roles: UserRole | UserRole[]) => { diff --git a/import_map.json b/import_map.json index 09d8231..07eebaf 100644 --- a/import_map.json +++ b/import_map.json @@ -3,6 +3,8 @@ "https://deno.land/std@v0.61.0/encoding/hex.ts": "https://deno.land/std@0.61.0/encoding/hex.ts", "https://deno.land/std@v0.61.0/encoding/base64url.ts": "https://deno.land/std@0.61.0/encoding/base64url.ts", "https://deno.land/std@v0.61.0/hash/sha256.ts": "https://deno.land/std@0.61.0/hash/sha256.ts", - "https://deno.land/std@v0.61.0/hash/sha512.ts": "https://deno.land/std@0.61.0/hash/sha512.ts" + "https://deno.land/std@v0.61.0/hash/sha512.ts": "https://deno.land/std@0.61.0/hash/sha512.ts", + "https://deno.land/std@0.53.0/path/posix.ts": "https://deno.land/std@0.61.0/path/posix.ts", + "https://deno.land/std@0.53.0/path/win32.ts": "https://deno.land/std@0.61.0/path/win32.ts" } } \ No newline at end of file diff --git a/middlewares/error.middleware.ts b/middlewares/error.middleware.ts index d8b46dd..70eded0 100644 --- a/middlewares/error.middleware.ts +++ b/middlewares/error.middleware.ts @@ -3,7 +3,7 @@ import { Status, } from "https://deno.land/x/oak@v5.0.0/mod.ts"; import { config } from "./../config/config.ts"; -import { Context } from "./../types.ts"; +import type { Context } from "./../types.ts"; const errorMiddleware = async (ctx: Context, next: () => Promise) => { try { diff --git a/middlewares/jwt-auth.middleware.ts b/middlewares/jwt-auth.middleware.ts index 2cd3cad..72e0bb7 100644 --- a/middlewares/jwt-auth.middleware.ts +++ b/middlewares/jwt-auth.middleware.ts @@ -1,4 +1,4 @@ -import { Context, AuthUser } from "./../types.ts"; +import type { Context, AuthUser } from "./../types.ts"; import { validateJwt } from "https://deno.land/x/djwt@v1.0/validate.ts"; import { JWTAlgorithm } from "./../helpers/jwt.ts"; diff --git a/middlewares/logger.middleware.ts b/middlewares/logger.middleware.ts index f7e9f8b..97ee985 100644 --- a/middlewares/logger.middleware.ts +++ b/middlewares/logger.middleware.ts @@ -1,4 +1,4 @@ -import { Context } from "./../types.ts"; +import type { Context } from "./../types.ts"; const loggerMiddleware = async (ctx: Context, next: () => Promise) => { await next(); const reqTime = ctx.response.headers.get("X-Response-Time"); diff --git a/middlewares/request-id.middleware.ts b/middlewares/request-id.middleware.ts index d673914..83eee46 100644 --- a/middlewares/request-id.middleware.ts +++ b/middlewares/request-id.middleware.ts @@ -1,4 +1,4 @@ -import { Context } from "./../types.ts"; +import type { Context } from "./../types.ts"; import { v4 as uuid } from "https://deno.land/std@0.62.0/uuid/mod.ts"; /** diff --git a/middlewares/request-validator.middleware.ts b/middlewares/request-validator.middleware.ts index 144e8a7..cb979f4 100644 --- a/middlewares/request-validator.middleware.ts +++ b/middlewares/request-validator.middleware.ts @@ -2,9 +2,9 @@ import { validate, ValidationErrors, ValidationRules, -} from "https://deno.land/x/validasaur@v0.7.0/src/mod.ts"; +} from "https://deno.land/x/validasaur@v0.15.0/mod.ts"; import { httpErrors } from "https://deno.land/x/oak@v5.0.0/mod.ts"; -import { Context } from "./../types.ts"; +import type { Context } from "./../types.ts"; /** * get single error message from errors diff --git a/middlewares/timing.middleware.ts b/middlewares/timing.middleware.ts index e2d3df1..5369fea 100644 --- a/middlewares/timing.middleware.ts +++ b/middlewares/timing.middleware.ts @@ -1,4 +1,4 @@ -import { Context } from "./../types.ts"; +import type { Context } from "./../types.ts"; const timingMiddleware = async (ctx: Context, next: () => Promise) => { const start = Date.now(); await next(); diff --git a/middlewares/user-guard.middleware.ts b/middlewares/user-guard.middleware.ts index 1ec0b44..53d18cd 100644 --- a/middlewares/user-guard.middleware.ts +++ b/middlewares/user-guard.middleware.ts @@ -1,5 +1,5 @@ import { httpErrors } from "https://deno.land/x/oak@v5.0.0/mod.ts"; -import { Context, UserRole } from "./../types.ts"; +import type { Context, UserRole } from "./../types.ts"; import { hasUserRole } from "../helpers/roles.ts"; diff --git a/repositories/user.repository.ts b/repositories/user.repository.ts index 897d5da..2a99d6c 100644 --- a/repositories/user.repository.ts +++ b/repositories/user.repository.ts @@ -1,5 +1,5 @@ import { db } from "./../db/db.ts"; -import { UserInfo } from "../types.ts"; +import type { UserInfo } from "../types.ts"; /** * Get all users list diff --git a/routes/auth.routes.ts b/routes/auth.routes.ts index fe157b7..25a8b32 100644 --- a/routes/auth.routes.ts +++ b/routes/auth.routes.ts @@ -8,7 +8,7 @@ import { required, isEmail, lengthBetween, -} from "https://deno.land/x/validasaur@v0.7.0/src/rules.ts"; +} from "https://deno.land/x/validasaur@v0.15.0/mod.ts"; import * as authService from "./../services/auth.service.ts"; import { requestValidator } from "./../middlewares/request-validator.middleware.ts"; diff --git a/routes/routes.ts b/routes/routes.ts index 0d08d07..6c97c2e 100644 --- a/routes/routes.ts +++ b/routes/routes.ts @@ -1,5 +1,5 @@ import { Router } from "https://deno.land/x/oak@v5.0.0/mod.ts"; -import { Context } from "./../types.ts"; +import type { Context } from "./../types.ts"; import * as authRoutes from "./auth.routes.ts"; import * as userRoutes from "./user.routes.ts"; diff --git a/routes/user.routes.ts b/routes/user.routes.ts index 152ca60..480929b 100644 --- a/routes/user.routes.ts +++ b/routes/user.routes.ts @@ -6,10 +6,10 @@ import { import { required, isEmail, -} from "https://deno.land/x/validasaur@v0.7.0/src/rules.ts"; +} from "https://deno.land/x/validasaur@v0.15.0/mod.ts"; import * as userService from "./../services/user.service.ts"; import { requestValidator, userGuard } from "./../middlewares/middlewares.ts"; -import { Context, UserRole } from "./../types.ts"; +import type { Context, UserRole } from "./../types.ts"; import { hasUserRole } from "../helpers/roles.ts"; /** request body schema for user create/update */ From 6d2b1d39d45fb1d1649877c6159f6004e4a0344c Mon Sep 17 00:00:00 2001 From: Gaurav Misra Date: Fri, 8 Oct 2021 16:53:43 -0700 Subject: [PATCH 3/6] - Update Oak to 6.2.0 - Update djwt to 1.1 - Pass seconds to setExpiration for djwt@v1.1 --- .env.example | 8 ++++---- README.md | 4 ++-- app.ts | 2 +- helpers/jwt.ts | 8 ++++---- helpers/roles.ts | 2 +- import_map.json | 4 +++- middlewares/error.middleware.ts | 2 +- middlewares/jwt-auth.middleware.ts | 2 +- middlewares/request-validator.middleware.ts | 2 +- middlewares/user-guard.middleware.ts | 2 +- routes/routes.ts | 2 +- routes/user.routes.ts | 5 +++-- services/auth.service.ts | 2 +- services/user.service.ts | 2 +- types/core/context.ts | 4 ++-- types/user/user-info.ts | 4 ++-- 16 files changed, 29 insertions(+), 26 deletions(-) diff --git a/.env.example b/.env.example index 27602d4..4c546ac 100644 --- a/.env.example +++ b/.env.example @@ -4,9 +4,9 @@ DB_PASS=example DB_USER=root ENV=dev -# Access token validity in ms -JWT_ACCESS_TOKEN_EXP=600000 -# Refresh token validity in ms -JWT_REFRESH_TOKEN_EXP=3600000 +# Access token validity in seconds +JWT_ACCESS_TOKEN_EXP=600 +# Refresh token validity in seconds +JWT_REFRESH_TOKEN_EXP=3600 # Secret secuirity string JWT_TOKEN_SECRET=HEGbulKGDblAFYskBLml \ No newline at end of file diff --git a/README.md b/README.md index 186c57c..aaccf7f 100644 --- a/README.md +++ b/README.md @@ -79,12 +79,12 @@ deno run --allow-net --allow-read --allow-write https://deno.land/x/nessie@v1.0. | Package | Purpose | | ---------|---------| -|[oak@v5.0.0](https://deno.land/x/oak@v5.0.0)| Deno middleware framework| +|[oak@v6.2.0](https://deno.land/x/oak@v6.2.0)| Deno middleware framework| |[dotenv@v0.4.2](https://deno.land/x/dotenv@v0.4.2)| Read env variables| |[mysql@2.2.0](https://deno.land/x/mysql@2.2.0)|MySQL driver for Deno| |[nessie@v1.0.0-rc3](https://deno.land/x/nessie@v1.0.0-rc3)| DB migration tool for Deno| |[validasaur@v0.7.0](https://deno.land/x/validasaur@v0.7.0)| validation library| -|[djwt@v1.0](https://deno.land/x/djwt@v1.0)| JWT token encoding| +|[djwt@v1.1](https://deno.land/x/djwt@v1.1)| JWT token encoding| |[bcrypt@v0.2.1](https://deno.land/x/bcrypt@v0.2.1)| bcrypt encription lib| ### Project Layout diff --git a/app.ts b/app.ts index 0f5259d..dd55091 100644 --- a/app.ts +++ b/app.ts @@ -1,4 +1,4 @@ -import { Application } from "https://deno.land/x/oak@v5.0.0/mod.ts"; +import { Application } from "https://deno.land/x/oak@v6.2.0/mod.ts"; import * as middlewares from "./middlewares/middlewares.ts"; import { oakCors } from "https://deno.land/x/cors/mod.ts"; import { router } from "./routes/routes.ts"; diff --git a/helpers/jwt.ts b/helpers/jwt.ts index b4408e5..cbcd79b 100644 --- a/helpers/jwt.ts +++ b/helpers/jwt.ts @@ -3,8 +3,8 @@ import { Payload, makeJwt, setExpiration, -} from "https://deno.land/x/djwt@v1.0/create.ts"; -import { validateJwt } from "https://deno.land/x/djwt@v1.0/validate.ts"; +} from "https://deno.land/x/djwt@v1.1/create.ts"; +import { validateJwt } from "https://deno.land/x/djwt@v1.1/validate.ts"; import { config } from "./../config/config.ts"; const { @@ -27,7 +27,7 @@ const getAuthToken = (user: any) => { name: user.name, email: user.email, roles: user.roles, - exp: setExpiration(new Date().getTime() + parseInt(JWT_ACCESS_TOKEN_EXP)), + exp: setExpiration((Date.now() / 1000) + parseInt(JWT_ACCESS_TOKEN_EXP)), }; return makeJwt({ header, payload, key: JWT_TOKEN_SECRET }); @@ -37,7 +37,7 @@ const getRefreshToken = (user: any) => { const payload: Payload = { iss: "deno-api", id: user.id, - exp: setExpiration(new Date().getTime() + parseInt(JWT_REFRESH_TOKEN_EXP)), + exp: setExpiration((Date.now() / 1000) + parseInt(JWT_REFRESH_TOKEN_EXP)), }; return makeJwt({ header, payload, key: JWT_TOKEN_SECRET }); diff --git a/helpers/roles.ts b/helpers/roles.ts index 36bb7b8..5177a89 100644 --- a/helpers/roles.ts +++ b/helpers/roles.ts @@ -1,5 +1,5 @@ import type { AuthUser } from "../types.ts"; -import { UserRole } from "../types/user/user-role.ts"; +import type { UserRole } from "../types/user/user-role.ts"; const hasUserRole = (user: AuthUser, roles: UserRole | UserRole[]) => { const userRoles = user.roles.split(",") diff --git a/import_map.json b/import_map.json index 07eebaf..dc466c1 100644 --- a/import_map.json +++ b/import_map.json @@ -5,6 +5,8 @@ "https://deno.land/std@v0.61.0/hash/sha256.ts": "https://deno.land/std@0.61.0/hash/sha256.ts", "https://deno.land/std@v0.61.0/hash/sha512.ts": "https://deno.land/std@0.61.0/hash/sha512.ts", "https://deno.land/std@0.53.0/path/posix.ts": "https://deno.land/std@0.61.0/path/posix.ts", - "https://deno.land/std@0.53.0/path/win32.ts": "https://deno.land/std@0.61.0/path/win32.ts" + "https://deno.land/std@0.53.0/path/win32.ts": "https://deno.land/std@0.61.0/path/win32.ts", + "https://deno.land/std@0.56.0/path/posix.ts": "https://deno.land/std@0.61.0/path/posix.ts", + "https://deno.land/std@0.56.0/path/win32.ts": "https://deno.land/std@0.61.0/path/win32.ts" } } \ No newline at end of file diff --git a/middlewares/error.middleware.ts b/middlewares/error.middleware.ts index 70eded0..a8ef15f 100644 --- a/middlewares/error.middleware.ts +++ b/middlewares/error.middleware.ts @@ -1,7 +1,7 @@ import { isHttpError, Status, -} from "https://deno.land/x/oak@v5.0.0/mod.ts"; +} from "https://deno.land/x/oak@v6.2.0/mod.ts"; import { config } from "./../config/config.ts"; import type { Context } from "./../types.ts"; diff --git a/middlewares/jwt-auth.middleware.ts b/middlewares/jwt-auth.middleware.ts index 72e0bb7..6a30731 100644 --- a/middlewares/jwt-auth.middleware.ts +++ b/middlewares/jwt-auth.middleware.ts @@ -1,5 +1,5 @@ import type { Context, AuthUser } from "./../types.ts"; -import { validateJwt } from "https://deno.land/x/djwt@v1.0/validate.ts"; +import { validateJwt } from "https://deno.land/x/djwt@v1.1/validate.ts"; import { JWTAlgorithm } from "./../helpers/jwt.ts"; /** diff --git a/middlewares/request-validator.middleware.ts b/middlewares/request-validator.middleware.ts index cb979f4..9bb0a12 100644 --- a/middlewares/request-validator.middleware.ts +++ b/middlewares/request-validator.middleware.ts @@ -3,7 +3,7 @@ import { ValidationErrors, ValidationRules, } from "https://deno.land/x/validasaur@v0.15.0/mod.ts"; -import { httpErrors } from "https://deno.land/x/oak@v5.0.0/mod.ts"; +import { httpErrors } from "https://deno.land/x/oak@v6.2.0/mod.ts"; import type { Context } from "./../types.ts"; /** diff --git a/middlewares/user-guard.middleware.ts b/middlewares/user-guard.middleware.ts index 53d18cd..bf3a2bd 100644 --- a/middlewares/user-guard.middleware.ts +++ b/middlewares/user-guard.middleware.ts @@ -1,4 +1,4 @@ -import { httpErrors } from "https://deno.land/x/oak@v5.0.0/mod.ts"; +import { httpErrors } from "https://deno.land/x/oak@v6.2.0/mod.ts"; import type { Context, UserRole } from "./../types.ts"; import { hasUserRole } from "../helpers/roles.ts"; diff --git a/routes/routes.ts b/routes/routes.ts index 6c97c2e..5db542e 100644 --- a/routes/routes.ts +++ b/routes/routes.ts @@ -1,4 +1,4 @@ -import { Router } from "https://deno.land/x/oak@v5.0.0/mod.ts"; +import { Router } from "https://deno.land/x/oak@v6.2.0/mod.ts"; import type { Context } from "./../types.ts"; import * as authRoutes from "./auth.routes.ts"; diff --git a/routes/user.routes.ts b/routes/user.routes.ts index 480929b..7d7c980 100644 --- a/routes/user.routes.ts +++ b/routes/user.routes.ts @@ -2,14 +2,15 @@ import { helpers, Status, httpErrors, -} from "https://deno.land/x/oak@v5.0.0/mod.ts"; +} from "https://deno.land/x/oak@v6.2.0/mod.ts"; import { required, isEmail, } from "https://deno.land/x/validasaur@v0.15.0/mod.ts"; import * as userService from "./../services/user.service.ts"; import { requestValidator, userGuard } from "./../middlewares/middlewares.ts"; -import type { Context, UserRole } from "./../types.ts"; +import type { Context} from "./../types.ts"; +import { UserRole} from "./../types.ts"; import { hasUserRole } from "../helpers/roles.ts"; /** request body schema for user create/update */ diff --git a/services/auth.service.ts b/services/auth.service.ts index b6ec166..4151d01 100644 --- a/services/auth.service.ts +++ b/services/auth.service.ts @@ -1,5 +1,5 @@ import * as userRepo from "./../repositories/user.repository.ts"; -import { httpErrors } from "https://deno.land/x/oak@v5.0.0/mod.ts"; +import { httpErrors } from "https://deno.land/x/oak@v6.2.0/mod.ts"; import * as encription from "../helpers/encription.ts"; import * as jwt from "../helpers/jwt.ts"; import { diff --git a/services/user.service.ts b/services/user.service.ts index 556236a..2a7b03d 100644 --- a/services/user.service.ts +++ b/services/user.service.ts @@ -1,5 +1,5 @@ import * as userRepo from "./../repositories/user.repository.ts"; -import { httpErrors } from "https://deno.land/x/oak@v5.0.0/mod.ts"; +import { httpErrors } from "https://deno.land/x/oak@v6.2.0/mod.ts"; import { encript } from "../helpers/encription.ts"; /** diff --git a/types/core/context.ts b/types/core/context.ts index 19cd774..37dec47 100644 --- a/types/core/context.ts +++ b/types/core/context.ts @@ -1,5 +1,5 @@ -import { Context as OakContext } from "https://deno.land/x/oak@v5.0.0/mod.ts"; -import { AuthUser } from "./../auth/auth-user.ts"; +import { Context as OakContext } from "https://deno.land/x/oak@v6.2.0/mod.ts"; +import type { AuthUser } from "./../auth/auth-user.ts"; /** * Custom appilication context diff --git a/types/user/user-info.ts b/types/user/user-info.ts index 5e2a3d5..1f6f619 100644 --- a/types/user/user-info.ts +++ b/types/user/user-info.ts @@ -1,5 +1,5 @@ -import { CreateUser } from "./create-user.ts"; -import { UserRole } from "./user-role.ts"; +import type { CreateUser } from "./create-user.ts"; +import type { UserRole } from "./user-role.ts"; /** Request body to create user */ export type UserInfo = CreateUser & { From 51808c4d4661d86fd0f64273f80c00fc26cc6664 Mon Sep 17 00:00:00 2001 From: Gaurav Misra Date: Fri, 8 Oct 2021 18:42:01 -0700 Subject: [PATCH 4/6] - Add MIN_PASSWORD_LENGTH to .env - Moved PORT to .env - Moved import map to import_map folder - Updated jwt to 1.4 - Added a map for mysql/connection.ts to fix import type error --- .env.example | 8 +- Dockerfile | 2 +- README.md | 4 +- app.ts | 5 +- helpers/jwt.ts | 4 +- import_map.json => importmap/map.json | 4 +- importmap/x/mysql@v2.7.0/src/connection.ts | 344 ++++++++++++++++++++ middlewares/jwt-auth.middleware.ts | 2 +- middlewares/request-validator.middleware.ts | 14 +- routes/auth.routes.ts | 28 +- services/auth.service.ts | 5 +- types.ts | 1 - types/auth/refresh-token.ts | 2 +- 13 files changed, 389 insertions(+), 34 deletions(-) rename import_map.json => importmap/map.json (78%) create mode 100644 importmap/x/mysql@v2.7.0/src/connection.ts diff --git a/.env.example b/.env.example index 4c546ac..ee80da2 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,6 @@ +# App +PORT=8000 + DB_NAME=deno_api_db DB_HOST=db DB_PASS=example @@ -9,4 +12,7 @@ JWT_ACCESS_TOKEN_EXP=600 # Refresh token validity in seconds JWT_REFRESH_TOKEN_EXP=3600 # Secret secuirity string -JWT_TOKEN_SECRET=HEGbulKGDblAFYskBLml \ No newline at end of file +JWT_TOKEN_SECRET=HEGbulKGDblAFYskBLml + +# Registration +MIN_PASSWORD_LENGTH=8 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index bf62cdc..9293299 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,4 +8,4 @@ COPY . . USER deno RUN deno cache --unstable --importmap import_map.json app.ts -CMD ["run", "--allow-read", "--allow-net", "--unstable", "--importmap import_map.json", "app.ts"] \ No newline at end of file +CMD ["run", "--allow-read", "--allow-net", "--unstable", "--importmap importmap/map.json", "app.ts"] \ No newline at end of file diff --git a/README.md b/README.md index aaccf7f..8bed67f 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ We can run the project **with/ without Docker**. - For non-docker run API server with Deno run time ``` - $ deno run --allow-read --allow-net --unstable --importmap import_map.json app.ts + $ deno run --allow-read --allow-net --unstable --importmap importmap/map.json app.ts ``` - **API** - Browse `API` at [http://localhost:8000](http://localhost:8000) @@ -84,7 +84,7 @@ deno run --allow-net --allow-read --allow-write https://deno.land/x/nessie@v1.0. |[mysql@2.2.0](https://deno.land/x/mysql@2.2.0)|MySQL driver for Deno| |[nessie@v1.0.0-rc3](https://deno.land/x/nessie@v1.0.0-rc3)| DB migration tool for Deno| |[validasaur@v0.7.0](https://deno.land/x/validasaur@v0.7.0)| validation library| -|[djwt@v1.1](https://deno.land/x/djwt@v1.1)| JWT token encoding| +|[djwt@v1.4](https://deno.land/x/djwt@v1.4)| JWT token encoding| |[bcrypt@v0.2.1](https://deno.land/x/bcrypt@v0.2.1)| bcrypt encription lib| ### Project Layout diff --git a/app.ts b/app.ts index dd55091..d91b0b1 100644 --- a/app.ts +++ b/app.ts @@ -5,7 +5,6 @@ import { router } from "./routes/routes.ts"; import type { Context } from "./types.ts"; import { config } from "./config/config.ts"; -const port = 8000; const app = new Application(); app.use(oakCors()); @@ -13,11 +12,11 @@ app.use(middlewares.loggerMiddleware); app.use(middlewares.errorMiddleware); app.use(middlewares.timingMiddleware); -const { JWT_TOKEN_SECRET } = config; +const { PORT, JWT_TOKEN_SECRET } = config; app.use(middlewares.JWTAuthMiddleware(JWT_TOKEN_SECRET)); app.use(middlewares.requestIdMiddleware); app.use(router.routes()); app.use(router.allowedMethods()); -await app.listen({ port }); +await app.listen({ port: Number.parseInt(PORT) }); diff --git a/helpers/jwt.ts b/helpers/jwt.ts index cbcd79b..a1c5b87 100644 --- a/helpers/jwt.ts +++ b/helpers/jwt.ts @@ -3,8 +3,8 @@ import { Payload, makeJwt, setExpiration, -} from "https://deno.land/x/djwt@v1.1/create.ts"; -import { validateJwt } from "https://deno.land/x/djwt@v1.1/validate.ts"; +} from "https://deno.land/x/djwt@v1.4/create.ts"; +import { validateJwt } from "https://deno.land/x/djwt@v1.4/validate.ts"; import { config } from "./../config/config.ts"; const { diff --git a/import_map.json b/importmap/map.json similarity index 78% rename from import_map.json rename to importmap/map.json index dc466c1..d225296 100644 --- a/import_map.json +++ b/importmap/map.json @@ -7,6 +7,8 @@ "https://deno.land/std@0.53.0/path/posix.ts": "https://deno.land/std@0.61.0/path/posix.ts", "https://deno.land/std@0.53.0/path/win32.ts": "https://deno.land/std@0.61.0/path/win32.ts", "https://deno.land/std@0.56.0/path/posix.ts": "https://deno.land/std@0.61.0/path/posix.ts", - "https://deno.land/std@0.56.0/path/win32.ts": "https://deno.land/std@0.61.0/path/win32.ts" + "https://deno.land/std@0.56.0/path/win32.ts": "https://deno.land/std@0.61.0/path/win32.ts", + "https://deno.land/x/mysql@v2.6.1/": "https://deno.land/x/mysql@v2.7.0/", + "https://deno.land/x/mysql@v2.7.0/src/connection.ts": "./importmap/x/mysql@v2.7.0/src/connection.ts" } } \ No newline at end of file diff --git a/importmap/x/mysql@v2.7.0/src/connection.ts b/importmap/x/mysql@v2.7.0/src/connection.ts new file mode 100644 index 0000000..9ad6905 --- /dev/null +++ b/importmap/x/mysql@v2.7.0/src/connection.ts @@ -0,0 +1,344 @@ +import type { ClientConfig } from "./client.ts"; +import { + ConnnectionError, + ProtocolError, + ReadError, + ResponseTimeoutError, +} from "./constant/errors.ts"; +import { log } from "./logger.ts"; +import { buildAuth } from "./packets/builders/auth.ts"; +import { buildQuery } from "./packets/builders/query.ts"; +import { ReceivePacket, SendPacket } from "./packets/packet.ts"; +import { parseError } from "./packets/parsers/err.ts"; +import { + AuthResult, + parseAuth, + parseHandshake, +} from "./packets/parsers/handshake.ts"; +import { FieldInfo, parseField, parseRow } from "./packets/parsers/result.ts"; +import { PacketType } from "./constant/packet.ts"; +import authPlugin from "./auth_plugin/index.ts"; + +/** + * Connection state + */ +export enum ConnectionState { + CONNECTING, + CONNECTED, + CLOSING, + CLOSED, +} + +/** + * Result for execute sql + */ +export type ExecuteResult = { + affectedRows?: number; + lastInsertId?: number; + fields?: FieldInfo[]; + rows?: any[]; + iterator?: any; +}; + +/** Connection for mysql */ +export class Connection { + state: ConnectionState = ConnectionState.CONNECTING; + capabilities: number = 0; + serverVersion: string = ""; + + private conn?: Deno.Conn = undefined; + private _timedOut = false; + + get remoteAddr(): string { + return this.config.socketPath + ? `unix:${this.config.socketPath}` + : `${this.config.hostname}:${this.config.port}`; + } + + constructor(readonly config: ClientConfig) {} + + private async _connect() { + // TODO: implement connect timeout + const { hostname, port = 3306, socketPath, username = "", password } = + this.config; + log.info(`connecting ${this.remoteAddr}`); + this.conn = !socketPath + ? await Deno.connect({ + transport: "tcp", + hostname, + port, + }) + : await Deno.connect({ + transport: "unix", + path: socketPath, + } as any); + + try { + let receive = await this.nextPacket(); + const handshakePacket = parseHandshake(receive.body); + const data = buildAuth(handshakePacket, { + username, + password, + db: this.config.db, + }); + + await new SendPacket(data, 0x1).send(this.conn); + + this.state = ConnectionState.CONNECTING; + this.serverVersion = handshakePacket.serverVersion; + this.capabilities = handshakePacket.serverCapabilities; + + receive = await this.nextPacket(); + + const authResult = parseAuth(receive); + let handler; + + switch (authResult) { + case AuthResult.AuthMoreRequired: + const adaptedPlugin = + (authPlugin as any)[handshakePacket.authPluginName]; + handler = adaptedPlugin; + break; + case AuthResult.MethodMismatch: + // TODO: Negotiate + throw new Error("Currently cannot support auth method mismatch!"); + } + + let result; + if (handler) { + result = handler.start(handshakePacket.seed, password!); + while (!result.done) { + if (result.data) { + const sequenceNumber = receive.header.no + 1; + await new SendPacket(result.data, sequenceNumber).send(this.conn); + receive = await this.nextPacket(); + } + if (result.quickRead) { + await this.nextPacket(); + } + if (result.next) { + result = result.next(receive); + } + } + } + + const header = receive.body.readUint8(); + if (header === 0xff) { + const error = parseError(receive.body, this); + log.error(`connect error(${error.code}): ${error.message}`); + this.close(); + throw new Error(error.message); + } else { + log.info(`connected to ${this.remoteAddr}`); + this.state = ConnectionState.CONNECTED; + } + + if (this.config.charset) { + await this.execute(`SET NAMES ${this.config.charset}`); + } + } catch (error) { + // Call close() to avoid leaking socket. + this.close(); + throw error; + } + } + + /** Connect to database */ + async connect(): Promise { + await this._connect(); + } + + private async nextPacket(): Promise { + if (!this.conn) { + throw new ConnnectionError("Not connected"); + } + + const timeoutTimer = this.config.timeout + ? setTimeout( + this._timeoutCallback, + this.config.timeout, + ) + : null; + let packet: ReceivePacket | null; + try { + packet = await new ReceivePacket().parse(this.conn!); + } catch (error) { + if (this._timedOut) { + // Connection has been closed by timeoutCallback. + throw new ResponseTimeoutError("Connection read timed out"); + } + timeoutTimer && clearTimeout(timeoutTimer); + this.close(); + throw error; + } + timeoutTimer && clearTimeout(timeoutTimer); + + if (!packet) { + // Connection is half-closed by the remote host. + // Call close() to avoid leaking socket. + this.close(); + throw new ReadError("Connection closed unexpectedly"); + } + if (packet.type === PacketType.ERR_Packet) { + packet.body.skip(1); + const error = parseError(packet.body, this); + throw new Error(error.message); + } + return packet!; + } + + private _timeoutCallback = () => { + log.info("connection read timed out"); + this._timedOut = true; + this.close(); + }; + + /** + * Check if database server version is less than 5.7.0 + * + * MySQL version is "x.y.z" + * eg "5.5.62" + * + * MariaDB version is "5.5.5-x.y.z-MariaDB[-build-infos]" for versions after 5 (10.0 etc) + * eg "5.5.5-10.4.10-MariaDB-1:10.4.10+maria~bionic" + * and "x.y.z-MariaDB-[build-infos]" for 5.x versions + * eg "5.5.64-MariaDB-1~trusty" + */ + private lessThan5_7(): Boolean { + const version = this.serverVersion; + if (!version.includes("MariaDB")) return version < "5.7.0"; + const segments = version.split("-"); + // MariaDB v5.x + if (segments[1] === "MariaDB") return segments[0] < "5.7.0"; + // MariaDB v10+ + return false; + } + + /** Check if the MariaDB version is 10.0 or 10.1 */ + private isMariaDBAndVersion10_0Or10_1(): Boolean { + const version = this.serverVersion; + if (!version.includes("MariaDB")) return false; + return version.includes("5.5.5-10.1") || version.includes("5.5.5-10.0"); + } + + /** Close database connection */ + close(): void { + if (this.state != ConnectionState.CLOSED) { + log.info("close connection"); + this.conn?.close(); + this.state = ConnectionState.CLOSED; + } + } + + /** + * excute query sql + * @param sql query sql string + * @param params query params + */ + async query(sql: string, params?: any[]): Promise { + const result = await this.execute(sql, params); + if (result && result.rows) { + return result.rows; + } else { + return result; + } + } + + /** + * execute sql + * @param sql sql string + * @param params query params + * @param iterator whether to return an ExecuteIteratorResult or ExecuteResult + */ + async execute( + sql: string, + params?: any[], + iterator = false, + ): Promise { + if (this.state != ConnectionState.CONNECTED) { + if (this.state == ConnectionState.CLOSED) { + throw new ConnnectionError("Connection is closed"); + } else { + throw new ConnnectionError("Must be connected first"); + } + } + const data = buildQuery(sql, params); + try { + await new SendPacket(data, 0).send(this.conn!); + let receive = await this.nextPacket(); + if (receive.type === PacketType.OK_Packet) { + receive.body.skip(1); + return { + affectedRows: receive.body.readEncodedLen(), + lastInsertId: receive.body.readEncodedLen(), + }; + } else if (receive.type !== PacketType.Result) { + throw new ProtocolError(); + } + let fieldCount = receive.body.readEncodedLen(); + const fields: FieldInfo[] = []; + while (fieldCount--) { + const packet = await this.nextPacket(); + if (packet) { + const field = parseField(packet.body); + fields.push(field); + } + } + + const rows = []; + if (this.lessThan5_7() || this.isMariaDBAndVersion10_0Or10_1()) { + // EOF(less than 5.7 or mariadb version is 10.0 or 10.1) + receive = await this.nextPacket(); + if (receive.type !== PacketType.EOF_Packet) { + throw new ProtocolError(); + } + } + + if (!iterator) { + while (true) { + receive = await this.nextPacket(); + if (receive.type === PacketType.EOF_Packet) { + break; + } else { + const row = parseRow(receive.body, fields); + rows.push(row); + } + } + return { rows, fields }; + } + + return { + fields, + iterator: this.buildIterator(fields), + }; + } catch (error) { + this.close(); + throw error; + } + } + + private buildIterator(fields: FieldInfo[]): any { + const next = async () => { + const receive = await this.nextPacket(); + + if (receive.type === PacketType.EOF_Packet) { + return { done: true }; + } + + const value = parseRow(receive.body, fields); + + return { + done: false, + value, + }; + }; + + return { + [Symbol.asyncIterator]: () => { + return { + next, + }; + }, + }; + } +} diff --git a/middlewares/jwt-auth.middleware.ts b/middlewares/jwt-auth.middleware.ts index 6a30731..578474e 100644 --- a/middlewares/jwt-auth.middleware.ts +++ b/middlewares/jwt-auth.middleware.ts @@ -1,5 +1,5 @@ import type { Context, AuthUser } from "./../types.ts"; -import { validateJwt } from "https://deno.land/x/djwt@v1.1/validate.ts"; +import { validateJwt } from "https://deno.land/x/djwt@v1.4/validate.ts"; import { JWTAlgorithm } from "./../helpers/jwt.ts"; /** diff --git a/middlewares/request-validator.middleware.ts b/middlewares/request-validator.middleware.ts index 9bb0a12..a3c1434 100644 --- a/middlewares/request-validator.middleware.ts +++ b/middlewares/request-validator.middleware.ts @@ -30,12 +30,14 @@ const requestValidator = ({ bodyRules }: { bodyRules: ValidationRules }) => { const request = ctx.request; const body = (await request.body()).value; - /** check rules */ - const [isValid, errors] = await validate(body, bodyRules); - if (!isValid) { - /** if error found, throw bad request error */ - const message = getErrorMessage(errors); - throw new httpErrors.BadRequest(message); + if (body) { + /** check rules */ + const [isValid, errors] = await validate(body, bodyRules); + if (!isValid) { + /** if error found, throw bad request error */ + const message = getErrorMessage(errors); + throw new httpErrors.BadRequest(message); + } } await next(); diff --git a/routes/auth.routes.ts b/routes/auth.routes.ts index 25a8b32..a9ec28b 100644 --- a/routes/auth.routes.ts +++ b/routes/auth.routes.ts @@ -1,18 +1,22 @@ -import { +import type { Context, CreateUser, - RefreshToken, LoginCredential, + RefreshToken, } from "./../types.ts"; import { required, isEmail, - lengthBetween, + minLength, } from "https://deno.land/x/validasaur@v0.15.0/mod.ts"; import * as authService from "./../services/auth.service.ts"; import { requestValidator } from "./../middlewares/request-validator.middleware.ts"; +import { config } from "./../config/config.ts"; + +const { MIN_PASSWORD_LENGTH } = config; + /** * request body schema * for user create/update @@ -20,7 +24,7 @@ import { requestValidator } from "./../middlewares/request-validator.middleware. const registrationSchema = { name: [required], email: [required, isEmail], - password: [required, lengthBetween(6, 12)], + password: [required, minLength(Number.parseInt(MIN_PASSWORD_LENGTH))], }; //todo: add validation alphanumeric, spechal char @@ -34,7 +38,7 @@ const register = [ /** router handler */ async (ctx: Context) => { const request = ctx.request; - const userData = (await request.body()).value as CreateUser; + const userData = await request.body().value as CreateUser; const user = await authService.registerUser(userData); ctx.response.body = user; }, @@ -46,7 +50,7 @@ const register = [ * */ const loginSchema = { email: [required, isEmail], - password: [required, lengthBetween(6, 12)], + password: [required, minLength(Number.parseInt(MIN_PASSWORD_LENGTH))], }; const login = [ @@ -55,14 +59,14 @@ const login = [ /** router handler */ async (ctx: Context) => { const request = ctx.request; - const credential = (await request.body()).value as LoginCredential; + const credential = await request.body().value as LoginCredential; const token = await authService.loginUser(credential); ctx.response.body = token; }, ]; const refreshTokenSchema = { - refresh_token: [required], + value: [required], }; const refreshToken = [ /** request validation middleware */ @@ -70,12 +74,10 @@ const refreshToken = [ /** router handler */ async (ctx: Context) => { const request = ctx.request; - const data = (await request.body()).value as RefreshToken; + const token = await request.body().value as RefreshToken; - const token = await authService.refreshToken( - data["refresh_token"], - ); - ctx.response.body = token; + const auth = await authService.jwtAuth(token); + ctx.response.body = auth; }, ]; diff --git a/services/auth.service.ts b/services/auth.service.ts index 4151d01..c260127 100644 --- a/services/auth.service.ts +++ b/services/auth.service.ts @@ -6,6 +6,7 @@ import { CreateUser, UserRole, UserInfo, + RefreshToken, LoginCredential, } from "../types.ts"; @@ -60,10 +61,10 @@ export const loginUser = async (credential: LoginCredential) => { throw new httpErrors.Unauthorized("Wrong credential"); }; -export const refreshToken = async (token: string) => { +export const jwtAuth = async (token: RefreshToken) => { try { // todo: check token intention - const payload = await jwt.getJwtPayload(token); + const payload = await jwt.getJwtPayload(token.value); if (payload) { /** get user from token */ const id = payload.id as number; diff --git a/types.ts b/types.ts index 9a3f930..ba08b7b 100644 --- a/types.ts +++ b/types.ts @@ -3,7 +3,6 @@ export * from "./types/auth/auth-user.ts"; export * from "./types/auth/login-credential.ts"; export * from "./types/auth/refresh-token.ts"; - export * from "./types/user/user-role.ts"; export * from "./types/user/create-user.ts"; export * from "./types/user/user-info.ts"; diff --git a/types/auth/refresh-token.ts b/types/auth/refresh-token.ts index fb808b4..3e0e3ca 100644 --- a/types/auth/refresh-token.ts +++ b/types/auth/refresh-token.ts @@ -1,5 +1,5 @@ /** Token refresh request body */ export type RefreshToken = { /** refresh token */ - refresh_token: string; + value: string; }; From d32cd413c3e25ae5286d93f3c809947e068f0e18 Mon Sep 17 00:00:00 2001 From: Gaurav Misra Date: Fri, 8 Oct 2021 19:10:46 -0700 Subject: [PATCH 5/6] Removed importmap folder --- Dockerfile | 2 +- README.md | 2 +- db/db.ts | 2 +- importmap/map.json => importmap.json | 4 +- importmap/x/mysql@v2.7.0/src/connection.ts | 344 --------------------- 5 files changed, 4 insertions(+), 350 deletions(-) rename importmap/map.json => importmap.json (78%) delete mode 100644 importmap/x/mysql@v2.7.0/src/connection.ts diff --git a/Dockerfile b/Dockerfile index 9293299..05f9322 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,4 +8,4 @@ COPY . . USER deno RUN deno cache --unstable --importmap import_map.json app.ts -CMD ["run", "--allow-read", "--allow-net", "--unstable", "--importmap importmap/map.json", "app.ts"] \ No newline at end of file +CMD ["run", "--allow-read", "--allow-net", "--unstable", "--importmap importmap.json", "app.ts"] \ No newline at end of file diff --git a/README.md b/README.md index 8bed67f..2c6d11d 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ We can run the project **with/ without Docker**. - For non-docker run API server with Deno run time ``` - $ deno run --allow-read --allow-net --unstable --importmap importmap/map.json app.ts + $ deno run --allow-read --allow-net --unstable --importmap importmap.json app.ts ``` - **API** - Browse `API` at [http://localhost:8000](http://localhost:8000) diff --git a/db/db.ts b/db/db.ts index 2112f64..c03ff5d 100644 --- a/db/db.ts +++ b/db/db.ts @@ -1,4 +1,4 @@ -import { Client } from "https://deno.land/x/mysql@v2.6.1/mod.ts"; +import { Client } from "https://deno.land/x/mysql@v2.7.0/mod.ts"; import { config } from "./../config/config.ts"; const port = config.DB_PORT ? parseInt(config.DB_PORT || "") : undefined; diff --git a/importmap/map.json b/importmap.json similarity index 78% rename from importmap/map.json rename to importmap.json index d225296..dc466c1 100644 --- a/importmap/map.json +++ b/importmap.json @@ -7,8 +7,6 @@ "https://deno.land/std@0.53.0/path/posix.ts": "https://deno.land/std@0.61.0/path/posix.ts", "https://deno.land/std@0.53.0/path/win32.ts": "https://deno.land/std@0.61.0/path/win32.ts", "https://deno.land/std@0.56.0/path/posix.ts": "https://deno.land/std@0.61.0/path/posix.ts", - "https://deno.land/std@0.56.0/path/win32.ts": "https://deno.land/std@0.61.0/path/win32.ts", - "https://deno.land/x/mysql@v2.6.1/": "https://deno.land/x/mysql@v2.7.0/", - "https://deno.land/x/mysql@v2.7.0/src/connection.ts": "./importmap/x/mysql@v2.7.0/src/connection.ts" + "https://deno.land/std@0.56.0/path/win32.ts": "https://deno.land/std@0.61.0/path/win32.ts" } } \ No newline at end of file diff --git a/importmap/x/mysql@v2.7.0/src/connection.ts b/importmap/x/mysql@v2.7.0/src/connection.ts deleted file mode 100644 index 9ad6905..0000000 --- a/importmap/x/mysql@v2.7.0/src/connection.ts +++ /dev/null @@ -1,344 +0,0 @@ -import type { ClientConfig } from "./client.ts"; -import { - ConnnectionError, - ProtocolError, - ReadError, - ResponseTimeoutError, -} from "./constant/errors.ts"; -import { log } from "./logger.ts"; -import { buildAuth } from "./packets/builders/auth.ts"; -import { buildQuery } from "./packets/builders/query.ts"; -import { ReceivePacket, SendPacket } from "./packets/packet.ts"; -import { parseError } from "./packets/parsers/err.ts"; -import { - AuthResult, - parseAuth, - parseHandshake, -} from "./packets/parsers/handshake.ts"; -import { FieldInfo, parseField, parseRow } from "./packets/parsers/result.ts"; -import { PacketType } from "./constant/packet.ts"; -import authPlugin from "./auth_plugin/index.ts"; - -/** - * Connection state - */ -export enum ConnectionState { - CONNECTING, - CONNECTED, - CLOSING, - CLOSED, -} - -/** - * Result for execute sql - */ -export type ExecuteResult = { - affectedRows?: number; - lastInsertId?: number; - fields?: FieldInfo[]; - rows?: any[]; - iterator?: any; -}; - -/** Connection for mysql */ -export class Connection { - state: ConnectionState = ConnectionState.CONNECTING; - capabilities: number = 0; - serverVersion: string = ""; - - private conn?: Deno.Conn = undefined; - private _timedOut = false; - - get remoteAddr(): string { - return this.config.socketPath - ? `unix:${this.config.socketPath}` - : `${this.config.hostname}:${this.config.port}`; - } - - constructor(readonly config: ClientConfig) {} - - private async _connect() { - // TODO: implement connect timeout - const { hostname, port = 3306, socketPath, username = "", password } = - this.config; - log.info(`connecting ${this.remoteAddr}`); - this.conn = !socketPath - ? await Deno.connect({ - transport: "tcp", - hostname, - port, - }) - : await Deno.connect({ - transport: "unix", - path: socketPath, - } as any); - - try { - let receive = await this.nextPacket(); - const handshakePacket = parseHandshake(receive.body); - const data = buildAuth(handshakePacket, { - username, - password, - db: this.config.db, - }); - - await new SendPacket(data, 0x1).send(this.conn); - - this.state = ConnectionState.CONNECTING; - this.serverVersion = handshakePacket.serverVersion; - this.capabilities = handshakePacket.serverCapabilities; - - receive = await this.nextPacket(); - - const authResult = parseAuth(receive); - let handler; - - switch (authResult) { - case AuthResult.AuthMoreRequired: - const adaptedPlugin = - (authPlugin as any)[handshakePacket.authPluginName]; - handler = adaptedPlugin; - break; - case AuthResult.MethodMismatch: - // TODO: Negotiate - throw new Error("Currently cannot support auth method mismatch!"); - } - - let result; - if (handler) { - result = handler.start(handshakePacket.seed, password!); - while (!result.done) { - if (result.data) { - const sequenceNumber = receive.header.no + 1; - await new SendPacket(result.data, sequenceNumber).send(this.conn); - receive = await this.nextPacket(); - } - if (result.quickRead) { - await this.nextPacket(); - } - if (result.next) { - result = result.next(receive); - } - } - } - - const header = receive.body.readUint8(); - if (header === 0xff) { - const error = parseError(receive.body, this); - log.error(`connect error(${error.code}): ${error.message}`); - this.close(); - throw new Error(error.message); - } else { - log.info(`connected to ${this.remoteAddr}`); - this.state = ConnectionState.CONNECTED; - } - - if (this.config.charset) { - await this.execute(`SET NAMES ${this.config.charset}`); - } - } catch (error) { - // Call close() to avoid leaking socket. - this.close(); - throw error; - } - } - - /** Connect to database */ - async connect(): Promise { - await this._connect(); - } - - private async nextPacket(): Promise { - if (!this.conn) { - throw new ConnnectionError("Not connected"); - } - - const timeoutTimer = this.config.timeout - ? setTimeout( - this._timeoutCallback, - this.config.timeout, - ) - : null; - let packet: ReceivePacket | null; - try { - packet = await new ReceivePacket().parse(this.conn!); - } catch (error) { - if (this._timedOut) { - // Connection has been closed by timeoutCallback. - throw new ResponseTimeoutError("Connection read timed out"); - } - timeoutTimer && clearTimeout(timeoutTimer); - this.close(); - throw error; - } - timeoutTimer && clearTimeout(timeoutTimer); - - if (!packet) { - // Connection is half-closed by the remote host. - // Call close() to avoid leaking socket. - this.close(); - throw new ReadError("Connection closed unexpectedly"); - } - if (packet.type === PacketType.ERR_Packet) { - packet.body.skip(1); - const error = parseError(packet.body, this); - throw new Error(error.message); - } - return packet!; - } - - private _timeoutCallback = () => { - log.info("connection read timed out"); - this._timedOut = true; - this.close(); - }; - - /** - * Check if database server version is less than 5.7.0 - * - * MySQL version is "x.y.z" - * eg "5.5.62" - * - * MariaDB version is "5.5.5-x.y.z-MariaDB[-build-infos]" for versions after 5 (10.0 etc) - * eg "5.5.5-10.4.10-MariaDB-1:10.4.10+maria~bionic" - * and "x.y.z-MariaDB-[build-infos]" for 5.x versions - * eg "5.5.64-MariaDB-1~trusty" - */ - private lessThan5_7(): Boolean { - const version = this.serverVersion; - if (!version.includes("MariaDB")) return version < "5.7.0"; - const segments = version.split("-"); - // MariaDB v5.x - if (segments[1] === "MariaDB") return segments[0] < "5.7.0"; - // MariaDB v10+ - return false; - } - - /** Check if the MariaDB version is 10.0 or 10.1 */ - private isMariaDBAndVersion10_0Or10_1(): Boolean { - const version = this.serverVersion; - if (!version.includes("MariaDB")) return false; - return version.includes("5.5.5-10.1") || version.includes("5.5.5-10.0"); - } - - /** Close database connection */ - close(): void { - if (this.state != ConnectionState.CLOSED) { - log.info("close connection"); - this.conn?.close(); - this.state = ConnectionState.CLOSED; - } - } - - /** - * excute query sql - * @param sql query sql string - * @param params query params - */ - async query(sql: string, params?: any[]): Promise { - const result = await this.execute(sql, params); - if (result && result.rows) { - return result.rows; - } else { - return result; - } - } - - /** - * execute sql - * @param sql sql string - * @param params query params - * @param iterator whether to return an ExecuteIteratorResult or ExecuteResult - */ - async execute( - sql: string, - params?: any[], - iterator = false, - ): Promise { - if (this.state != ConnectionState.CONNECTED) { - if (this.state == ConnectionState.CLOSED) { - throw new ConnnectionError("Connection is closed"); - } else { - throw new ConnnectionError("Must be connected first"); - } - } - const data = buildQuery(sql, params); - try { - await new SendPacket(data, 0).send(this.conn!); - let receive = await this.nextPacket(); - if (receive.type === PacketType.OK_Packet) { - receive.body.skip(1); - return { - affectedRows: receive.body.readEncodedLen(), - lastInsertId: receive.body.readEncodedLen(), - }; - } else if (receive.type !== PacketType.Result) { - throw new ProtocolError(); - } - let fieldCount = receive.body.readEncodedLen(); - const fields: FieldInfo[] = []; - while (fieldCount--) { - const packet = await this.nextPacket(); - if (packet) { - const field = parseField(packet.body); - fields.push(field); - } - } - - const rows = []; - if (this.lessThan5_7() || this.isMariaDBAndVersion10_0Or10_1()) { - // EOF(less than 5.7 or mariadb version is 10.0 or 10.1) - receive = await this.nextPacket(); - if (receive.type !== PacketType.EOF_Packet) { - throw new ProtocolError(); - } - } - - if (!iterator) { - while (true) { - receive = await this.nextPacket(); - if (receive.type === PacketType.EOF_Packet) { - break; - } else { - const row = parseRow(receive.body, fields); - rows.push(row); - } - } - return { rows, fields }; - } - - return { - fields, - iterator: this.buildIterator(fields), - }; - } catch (error) { - this.close(); - throw error; - } - } - - private buildIterator(fields: FieldInfo[]): any { - const next = async () => { - const receive = await this.nextPacket(); - - if (receive.type === PacketType.EOF_Packet) { - return { done: true }; - } - - const value = parseRow(receive.body, fields); - - return { - done: false, - value, - }; - }; - - return { - [Symbol.asyncIterator]: () => { - return { - next, - }; - }, - }; - } -} From 04257f60902bc295bcfb6c0dd1546881f7ddadd0 Mon Sep 17 00:00:00 2001 From: Gaurav Misra Date: Fri, 8 Oct 2021 19:13:37 -0700 Subject: [PATCH 6/6] Updated support statemnet to support Deno 1.4.2 in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2c6d11d..5d965b6 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This is a starter project to create Deno RESTful API using oak. [oak](https://github.com/oakserver/oak) is a middleware framework and router middleware for Deno, inspired by popular Node.js framework [Koa](https://koajs.com/) and [@koa/router](https://github.com/koajs/router/). -Note: Only Deno 1.0 is supported at the moment (work is ongoing to support newer versions). +Note: Only Deno 1.4.2 is supported at the moment (work is ongoing to support newer versions). This project covers - Swagger Open API doc