Skip to content
This repository was archived by the owner on Jul 25, 2024. It is now read-only.

MongoDB #11

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion adonis-typings/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
declare module "@ioc:Adonis/Addons/Jwt" {
// import mongoose from "mongoose";
import {
DatabaseTokenProviderConfig,
RedisTokenProviderConfig,
Expand All @@ -12,6 +13,26 @@ declare module "@ioc:Adonis/Addons/Jwt" {
import { DateTime } from "luxon";
import { JWTPayload } from "jose/jwt/verify";

// export type MongoTokenProviderConfig = {
// driver: 'mongo';
// model: typeof mongoose.Model;
// foreignKey?: string;
// connection?: string;
// type?: string;
// }
export type MongoTokenProviderConfig = {
driver: 'mongo';
model: {
find: Function,
findOne: Function,
deleteOne: Function,
create: Function,
};
foreignKey?: string;
connection?: string;
type?: string;
}

export type JWTCustomPayloadData = {
[key: string]: any;
};
Expand Down Expand Up @@ -83,7 +104,7 @@ declare module "@ioc:Adonis/Addons/Jwt" {
/**
* Provider for managing tokens
*/
tokenProvider: DatabaseTokenProviderConfig | RedisTokenProviderConfig;
tokenProvider: DatabaseTokenProviderConfig | RedisTokenProviderConfig | MongoTokenProviderConfig;

/**
* User provider
Expand Down
4 changes: 2 additions & 2 deletions lib/Guards/JwtGuard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ export class JWTGuard extends BaseGuard<"jwt"> implements JWTGuardContract<any,
/**
* Returns the bearer token
*/
private getBearerToken(): string {
public getBearerToken(): string {
/**
* Ensure the "Authorization" header value exists
*/
Expand All @@ -487,7 +487,7 @@ export class JWTGuard extends BaseGuard<"jwt"> implements JWTGuardContract<any,
/**
* Verify the token received in the request.
*/
private async verifyToken(token: string): Promise<JWTCustomPayload> {
public async verifyToken(token: string): Promise<JWTCustomPayload> {
const secret = this.generateKey(this.config.privateKey);

const { payload } = await jwtVerify(token, secret, {
Expand Down
102 changes: 102 additions & 0 deletions lib/TokenProviders/AbstractMongoProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"use strict";

import { DateTime } from "luxon";
import { DatabaseContract, QueryClientContract } from "@ioc:Adonis/Lucid/Database";
import { TokenProviderContract, ProviderTokenContract } from "@ioc:Adonis/Addons/Auth";
import { MongoTokenProviderConfig } from "@ioc:Adonis/Addons/Jwt";
import { ProviderToken } from "@adonisjs/auth/build/src/Tokens/ProviderToken";

/**
* Database backend tokens provider.
* Can't extend original TokenDatabaseProvider since all its methods are private,
* so I copied it altogether from @adonisjs/auth
*/
export default class AbstractDatabaseProvider implements TokenProviderContract {
constructor(protected config: MongoTokenProviderConfig, protected db: DatabaseContract) {}

/**
* Custom connection or query client
*/
protected connection?: string | QueryClientContract;

/**
* Returns the query client for database queries
*/
protected getQueryClient() {
return this.config.model;
}

/**
* The foreign key column
*/
protected foreignKey = this.config.foreignKey || "user_id";

/**
* Returns the builder query for a given token hash + type
*/
// protected getLookupQuery(tokenHash: string, tokenType: string) {
// return this.getQueryClient().from(this.config.table)
// .where("token", tokenHash)
// .where("type", tokenType);
// }

/**
* Define custom connection
*/
public setConnection(connection: string | QueryClientContract): this {
this.connection = connection;
return this;
}

/**
* Reads the token using the lookup token id
*/
public async read(_tokenId: string, _tokenHash: string, _tokenType: string): Promise<ProviderTokenContract | null> {
throw new Error("Subclass should overwrite this method");
}

/**
* Saves the token and returns the persisted token lookup id.
*/
public async write(_token: ProviderToken): Promise<string> {
throw new Error("Subclass should overwrite this method");
}

/**
* Removes a given token
*/
public async destroyWithHash(tokenHash: string, tokenType: string) {
if (!tokenHash) {
throw new Error("Empty token hash passed");
}
if (!tokenType) {
throw new Error("Empty token type passed");
}

await this.config.model.deleteOne({token: tokenHash, type: tokenType});
}

/**
* Removes a given token
*/
public async destroy(_tokenId: string, _tokenType: string) {
throw new Error("Should not use this function");
}

protected normalizeDatetime(expiresAt) {
let normalizedExpiryDate: undefined | DateTime;

/**
* Parse dialect date to an instance of Luxon
*/
if (expiresAt instanceof Date) {
normalizedExpiryDate = DateTime.fromJSDate(expiresAt);
} else if (expiresAt && typeof expiresAt === "string") {
normalizedExpiryDate = DateTime.fromJSDate(new Date(expiresAt));
} else if (expiresAt && typeof expiresAt === "number") {
normalizedExpiryDate = DateTime.fromMillis(expiresAt);
}

return normalizedExpiryDate;
}
}
147 changes: 147 additions & 0 deletions lib/TokenProviders/JwtMongoProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
"use strict";

import { Exception } from "@poppinss/utils";
import { DateTime } from "luxon";
import AbstractMongoProvider from "./AbstractMongoProvider";
import { JwtProviderToken } from "../ProviderToken/JwtProviderToken";
import { JwtProviderTokenContract, JwtProviderContract } from "@ioc:Adonis/Addons/Jwt";
import { ProviderToken } from "@adonisjs/auth/build/src/Tokens/ProviderToken";
import { ProviderTokenContract } from "@ioc:Adonis/Addons/Auth";

/**
* Database backend tokens provider.
* Can't extend original TokenDatabaseProvider since all its methods are private,
* so I copied it altogether from @adonisjs/auth
*/
export default class JwtMongoProvider extends AbstractMongoProvider implements JwtProviderContract {
/**
* Reads the token using the lookup token hash
*/
public async read(tokenId: string, tokenHash: string, tokenType: string): Promise<JwtProviderTokenContract | null> {
/**
* should not be provided
*/
if (tokenId) {
throw new Exception("Should not pass tokenId");
}

if (!tokenHash) {
throw new Exception("Empty token hash passed");
}
if (!tokenType) {
throw new Exception("Empty token type passed");
}

/**
* Find token using hash
*/
const tokenRow = await this.config.model.findOne({token: tokenHash, type: tokenType});
if (!tokenRow || !tokenRow.token) {
return null;
}

const {
name,
[this.foreignKey]: userId,
token: value,
refresh_token: refreshToken,
refresh_token_expires_at: refreshTokenExpiresAt,
type,
...meta
} = tokenRow;

/**
* token.expiresAt is not filled since JWT already contains an expiration date.
*/
const token = new JwtProviderToken(name, value, userId, type);
token.meta = meta;
token.refreshToken = refreshToken;
token.refreshTokenExpiresAt = refreshTokenExpiresAt;
return token;
}

/**
* Returns the builder query for a given refresh token hash
*/
// protected getRefreshTokenLookupQuery(tokenHash: string) {
// return this.config.model.findOne({refresh_token: tokenHash})
// }

/**
* Reads the refresh token using the token hash
*/
public async readRefreshToken(userRefreshToken: string, _tokenType: string): Promise<ProviderTokenContract | null> {
/**
* Find token using hash
*/
const tokenRow = await this.config.model.findOne({refresh_token: userRefreshToken});
if (!tokenRow || !tokenRow.token) {
return null;
}

const {
name,
[this.foreignKey]: userId,
token: value,
refresh_token: refreshToken,
refresh_token_expires_at: refreshTokenExpiresAt,
type,
...meta
} = tokenRow;

/**
* Ensure refresh token isn't expired
*/
const normalizedExpiryDate = this.normalizeDatetime(refreshTokenExpiresAt);
if (normalizedExpiryDate && normalizedExpiryDate.diff(DateTime.local(), "milliseconds").milliseconds <= 0) {
return null;
}


/**
* This is a ProviderToken with refresh token only (no JWT)
*/
const token = new ProviderToken(name, refreshToken, userId, type);
token.meta = meta;
return token;
}

/**
* Saves the token and returns the persisted token lookup id.
*/
public async write(token: JwtProviderToken): Promise<string> {
/**
* Payload to save to the database
*/
const payload = {
[this.foreignKey]: token.userId,
name: token.name,
token: token.tokenHash,
type: token.type,
refresh_token: token.refreshToken,
refresh_token_expires_at: token.refreshTokenExpiresAt,
expires_at: token.expiresAt,
created_at: new Date(),
...token.meta,
};

await this.config.model.create(payload);

const persistedToken = this.config.model.findOne({refresh_token: token.refreshToken});
return String(persistedToken['id']);
}

/**
* Removes a given token using hash
*/
public async destroyRefreshToken(tokenHash: string, tokenType: string): Promise<void> {
if (!tokenHash) {
throw new Error("Empty token hash passed");
}
if (!tokenType) {
throw new Error("Empty token type passed");
}

await this.config.model.deleteOne({refresh_token: tokenHash});
}
}
Loading