Skip to content

Commit

Permalink
🚀 Upgrading the auth system
Browse files Browse the repository at this point in the history
  • Loading branch information
Ashu11-A committed May 25, 2024
1 parent 6675d70 commit c091fd8
Show file tree
Hide file tree
Showing 4 changed files with 243 additions and 171 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ build
release
.vscode/settings.json
/pkg-cache
*.cjs
*.cjs
core/.key
56 changes: 21 additions & 35 deletions core/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { existsSync } from 'fs'
import { exists } from 'fs-extra'
import { readFile, rm, writeFile } from 'fs/promises'
import { join } from 'path'
import { argv, cwd } from 'process'
import prompts, { PromptObject } from 'prompts'
import prompts from 'prompts'
import 'reflect-metadata'
import { env, PKG_MODE } from '.'
import { Plugins } from './controller/plugins'
import { SocketController } from './controller/socket'
import { Auth, encrypt } from './functions/key'
import { Auth } from '@/controller/auth'
import { generatePort } from './functions/port'

interface Args {
Expand All @@ -26,22 +25,9 @@ const argsList: Args[] = [
(async () => {
prompts.override((await import('yargs')).argv)

if (!(await exists('.key'))) {
const questions: PromptObject<string>[] = [
{ name: 'email', message: 'Email', type: 'text', warn: 'Apenas cadastrado em paymentbot.com' },
{ name: 'password', message: 'Senha', type: 'password', warn: 'Apenas cadastrado em paymentbot.com' },
{ name: 'token', message: 'Token', type: 'password', warn: 'Visível no Dashboard' }
]

const response = await prompts(questions)

if (Object.keys(response).length !== 3 || Object.entries(response).filter(([, content]) => content === '').length > 0) throw new Error('Formulário não respondido!')
await encrypt(JSON.stringify(response))
}

const auth = new Auth()

await auth.init()

await auth.login()
await auth.validator()

Expand All @@ -65,30 +51,30 @@ const argsList: Args[] = [
}

switch (args[argNum]) {
case 'info': {
const packageJSON = JSON.parse(await readFile(join(__dirname, '../package.json'), { encoding: 'utf-8' })) as Record<string, string | object | []>
const infos = ['name', 'version', 'description', 'author', 'license'].reverse()
console.info(Object.entries(packageJSON).reverse().filter(([key]) => infos.includes(key)).reduce((object, [key, value]) => ({ [key]: value, ...object }), {}))
break
}
case 'port': {
argNum++
socket.listen(args[argNum])
break
}
case 'info': {
const packageJSON = JSON.parse(await readFile(join(__dirname, '../package.json'), { encoding: 'utf-8' })) as Record<string, string | object | []>
const infos = ['name', 'version', 'description', 'author', 'license'].reverse()
console.info(Object.entries(packageJSON).reverse().filter(([key]) => infos.includes(key)).reduce((object, [key, value]) => ({ [key]: value, ...object }), {}))
break
}
case 'port': {
argNum++
socket.listen(args[argNum])
break
}
}
}

['SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGILL', 'SIGTRAP', 'SIGABRT',
'SIGBUS', 'SIGFPE', 'SIGUSR1', 'SIGSEGV', 'SIGUSR2', 'SIGTERM'
].forEach(function (sig) {
process.on(sig, async function () {
if (PKG_MODE) {
for await (const plugin of await SocketController.io.fetchSockets()) {
if (plugin) plugin.emit('kill')
}
process.on(sig, async function () {
if (PKG_MODE) {
for await (const plugin of await SocketController.io.fetchSockets()) {
if (plugin) plugin.emit('kill')
}
process.exit()
});
}
process.exit()
});
});
})()
220 changes: 220 additions & 0 deletions core/src/controller/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import { Blowfish, enc } from 'crypto-js'
import { readFile, rm, writeFile } from 'fs/promises'
import { RootPATH } from '..'
import { api, key } from '../../package.json'
import { CronJob } from 'cron'
import prompts, { Choice, PromptObject } from 'prompts'
import { exists } from 'fs-extra'

type Credentials = {
email: string
password: string
token: string
}

interface User {
name: string;
email: string;
uuid: string;
}

interface AccessToken {
token: string;
expireIn: number;
}

interface RefreshToken {
token: string;
expireIn: number;
}

interface AuthData {
user: User;
accessToken: AccessToken;
refreshToken: RefreshToken;
}
interface BotInfo {
uuid: string;
name: string;
enabled: boolean;
expired: boolean;
expire_at: string;
created_at: string;
}

export class Auth {
public static user: User
public static accessToken: AccessToken
public static bot: BotInfo | undefined
private credentials: Credentials | undefined

async askCredentials ({ question }: { question?: (keyof Credentials)[] }): Promise<Credentials> {
const questions: PromptObject<string>[] = [
{ name: 'email', message: 'Email', type: 'text', warn: 'Apenas cadastrado em paymentbot.com' },
{ name: 'password', message: 'Senha', type: 'password', warn: 'Apenas cadastrado em paymentbot.com' },
{ name: 'token', message: 'Token', type: 'password', warn: 'Visível no Dashboard' }
]

const filter = questions.filter((propmt) => question === undefined || question?.includes(propmt.name as keyof Credentials))
const response = await prompts(filter) as Credentials

if (Object.keys(response).length !== filter.length || Object.entries(response).filter(([, content]) => content === '').length > 0) throw new Error('Formulário não respondido!')
await this.encryptCredentials(response)
return response
}

async encryptCredentials(credentials: Credentials): Promise<void> {
console.log(credentials)
credentials = {
...(await this.decryptCredentials()),
...credentials
}
console.log(credentials)
await writeFile(`${RootPATH}/.key`, Blowfish.encrypt(JSON.stringify(credentials), key).toString())
}

async decryptCredentials(): Promise<Credentials> {
const encrypted = await exists(`${RootPATH}/.key`) ? Blowfish.decrypt(await readFile(`${RootPATH}/.key`, { encoding: 'utf-8' }), key).toString(enc.Utf8) : '{}'
return JSON.parse(encrypted)
}


async initialize() {
const credentials = await this.decryptCredentials()
const keys = ['email', 'password', 'token']
let hasError = false

for (const key of keys) {
const credential = credentials[key as keyof Credentials]
if (credential === undefined || credential === '') hasError = true
}
if (hasError) {
await this.askCredentials({})
await this.initialize()
return
}
this.credentials = credentials
}

async login(): Promise<AuthData | undefined> {
if (this.credentials === undefined) {
await this.initialize()
}

const response = await fetch(`${api}/auth/login`, {
method: 'POST',
body: JSON.stringify({ email: this.credentials?.email, password: this.credentials?.password }),
headers: {
"Content-Type": "application/json",
}
})

if (!response.ok) {
const choices: Choice[] = [
{ title: 'Deslogar', description: 'Removerá seção atual', value: 'logout' },
{ title: 'Tentar Novamente', description: 'Tentar novamente fazer o login', value: 'try_again' }
]

const conclusion = await prompts({
type: 'select',
name: 'Error',
message: `Erro ${response.statusText} ao tentar logar!`,
choices,
initial: 1
})

switch (conclusion.Error) {
case 'logout': {
await this.logout()
await this.askCredentials({})
await this.initialize()
await this.login()
}
case 'try_again': {
await this.login()
}
}
return
}
const data = await response.json() as AuthData

Auth.user = data.user
Auth.accessToken = data.accessToken

console.log(`\n👋 Olá ${data.user.name}\n`)
return data
}


async logout () {
await rm(`${RootPATH}/.key`)
}

async validator() {
if (this.credentials === undefined) {
await this.initialize()
}

const response = await fetch(`${api}/bots/${this.credentials?.token}`, {
headers: {
Authorization: `Bearer ${Auth.accessToken.token}`
}
})

if (!response.ok) {
console.log(`☝️ Então ${Auth.user.name}, não achei o registro do seu bot!`)
const choices: Choice[] = [
{ title: 'Mudar Token', value: 'change' },
{ title: 'Tentar Novamente', value: 'try_again' },
{ title: 'Deslogar', value: 'logout' }
]

const conclusion = await prompts({
name: 'Error',
type: 'select',
choices,
message: `Ocorreu um erro ${response.statusText}`,
initial: 1
})

switch (conclusion.Error) {
case 'change': {
await this.askCredentials({ question: ['token'] })
await this.initialize()
await this.login()
await this.validator()
break
}
case 'try_again': {
await this.validator()
break
}
case 'logout': {
await this.logout()
await this.askCredentials({})
await this.initialize()
await this.login()
await this.validator()
break
}
}

return
}

const data = await response.json() as BotInfo

if (data.expired) {
console.log('❌ Bot expirou!')
} else if (!data.enabled) {
console.log('❌ Bot desabilitado!')
}

Auth.bot = data
}

async startCron (): Promise<void> {
const job = new CronJob('* * * * *', () => this.validator())
job.start()
}
}
Loading

0 comments on commit c091fd8

Please sign in to comment.