Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/zkdb as a service #47

Merged
merged 15 commits into from
Nov 10, 2023
Prev Previous commit
Next Next commit
Add helper for service
  • Loading branch information
chiro-hiro committed Oct 29, 2023
commit 290160afd523e3bb77ff75769dc10711a07dd111
4 changes: 4 additions & 0 deletions packages/serverless/src/helper/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface AppContext {
token?: string;
userId?: number;
}
42 changes: 42 additions & 0 deletions packages/serverless/src/helper/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { fileURLToPath, pathToFileURL } from 'url';
import path from 'path';
import { ConfigLoader, Singleton, Utilities } from '@orochi-network/framework';
import Joi from 'joi';

export const nodeEnvValue = ['development', 'production', 'staging'] as const;

type TNodeEnv = (typeof nodeEnvValue)[number];

interface IAppConfiguration {
nodeEnv: TNodeEnv;
merkle: string;
mongodbUrl: string;
redisUrl: string;
}

export const envLocation = `${Utilities.File.getRootFolder(
path.dirname(fileURLToPath(pathToFileURL(__filename).toString()))
)}/.env`;

console.log(envLocation);

const configLoader = Singleton<ConfigLoader>(
'zkdb-aas',
ConfigLoader,
envLocation,
Joi.object<IAppConfiguration>({
nodeEnv: Joi.string()
.required()
.trim()
.valid(...nodeEnvValue),
mongodbUrl: Joi.string()
.trim()
.required()
.regex(/^mongodb[\+a-z]{0}:\/\//),
redisUrl: Joi.string().trim(),
})
);

export const config: IAppConfiguration = configLoader.getConfig();

export default config;
27 changes: 27 additions & 0 deletions packages/serverless/src/helper/google-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Singleton } from '@orochi-network/framework';
import {
LoginTicket,
OAuth2Client,
VerifyIdTokenOptions,
} from 'google-auth-library';

export class GoogleOAuth2 {
private googleClient: OAuth2Client;

constructor() {
this.googleClient = new OAuth2Client();
}

public async verifyIdToken(
verifyOptions: VerifyIdTokenOptions
): Promise<LoginTicket> {
return this.googleClient.verifyIdToken(verifyOptions);
}
}

const GoogleOAuth2Instance = Singleton<GoogleOAuth2>(
'google-oauth2',
GoogleOAuth2
);

export default GoogleOAuth2Instance;
74 changes: 74 additions & 0 deletions packages/serverless/src/helper/jwt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { GraphQLError } from 'graphql';
import * as jose from 'jose';
import logger from './logger';
import config from './config';
import { Singleton } from '@orochi-network/framework';

export interface IJWTAuthenticationPayload extends jose.JWTPayload {
uuid: string;
email: string;
name?: string;
firstName?: string;
lastName?: string;
zkDatabase?: boolean;
}

export const jwtExpired = 7200;

export class JWTAuthentication<T extends jose.JWTPayload> {
private secret: Uint8Array;

constructor(secret: string) {
this.secret = jose.base64url.decode(secret);
}

public async sign(payload: T): Promise<string> {
if (config.nodeEnv === 'development') {
// Development token won't be expired
return new jose.SignJWT(payload)
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.sign(this.secret);
}
return new jose.SignJWT(payload)
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('2h')
.sign(this.secret);
}

public async verify(
token: string
): Promise<{ payload: T } & Pick<jose.JWTVerifyResult, 'protectedHeader'>> {
const { payload, protectedHeader } = await jose.jwtVerify(
token,
this.secret
);
return { payload: payload as T, protectedHeader };
}

public async verifyHeader(header: string): Promise<T> {
// Skip for development
try {
// 7 is length of `Bearer + space`
const { payload } = await this.verify(header.substring(7));
return payload || undefined;
} catch (e) {
logger.error(e);
throw new GraphQLError('User is not authenticated', {
extensions: {
code: 'UNAUTHENTICATED',
http: { status: 401 },
},
});
}
}
}

export const JWTAuthenInstance = Singleton(
'jwt-authentication',
JWTAuthentication<IJWTAuthenticationPayload>,
config.hmacSecretKey
);

export default JWTAuthenInstance;
3 changes: 3 additions & 0 deletions packages/serverless/src/helper/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { LoggerLoader } from '@orochi-network/framework';

export default new LoggerLoader('zkDatabase', 'debug', 'string');
72 changes: 72 additions & 0 deletions packages/serverless/src/helper/redis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Singleton } from '@orochi-network/framework';
import { SetOptions, createClient } from 'redis';
import config from './config';
import logger from './logger';

export class RedisClient {
private _client;

constructor(url: string) {
this._client = createClient({
url,
});
this._client.on('error', (err) => logger.error('Redis client error', err));
}

public async connect() {
try {
await this._client.connect();
logger.info('Redis client connected to', config.redisUrl);
} catch (error) {
logger.error('Failed to connect to redis', error);
}
}

public async get(key: string): Promise<string | null> {
return this._client.get(key);
}

public async set(
key: string,
value: string,
options?: SetOptions
): Promise<string | null> {
return this._client.set(key, value, options);
}

public async setEx(
key: string,
seconds: number,
value: string
): Promise<string> {
return this._client.setEx(key, seconds, value);
}

public async delete(key: string): Promise<number> {
return this._client.del(key);
}

public async listPush(key: string, value: string) {
return this._client.lPush(key, value);
}

public async expireAt(key: string, timestamp: number) {
return this._client.expireAt(key, timestamp);
}

public async listGetAll(key: string): Promise<string[]> {
return this._client.lRange(key, 0, -1);
}

public listRemoveItem(key: string, value: string): Promise<number> {
return this._client.lRem(key, 0, value);
}
}

export const RedisInstance = Singleton<RedisClient>(
'redis-client',
RedisClient,
config.redisUrl
);

export default RedisInstance;
23 changes: 23 additions & 0 deletions packages/serverless/src/helper/response-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Singleton } from '@orochi-network/framework';

export class AppErrorClass extends Error {
public statusCode: number;

public message: string;

constructor(statusCode: number = 500, message: string = 'Something wrong') {
super(message);
this.statusCode = statusCode;
this.message = message;
}
}

const AppError = (statusCode: number, message: string) =>
Singleton<AppErrorClass>(
`[AppError:${statusCode}]${message}`,
AppErrorClass,
statusCode,
message
);

export default AppError;