Skip to content
This repository has been archived by the owner on Apr 23, 2024. It is now read-only.

Commit

Permalink
Merge pull request #170 from mgilangjanuar/staging
Browse files Browse the repository at this point in the history
Release v1.4.0
  • Loading branch information
mgilangjanuar authored Jan 6, 2022
2 parents 9b768dd + f512704 commit a96dd00
Show file tree
Hide file tree
Showing 8 changed files with 329 additions and 92 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "teledrive",
"version": "1.3.1",
"version": "1.4.0",
"repository": "git@github.com:mgilangjanuar/teledrive.git",
"author": "M Gilang Januar <mgilangjanuar@gmail.com>",
"license": "MIT",
Expand Down
4 changes: 2 additions & 2 deletions server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "server",
"version": "1.3.1",
"version": "1.4.0",
"main": "dist/index.js",
"license": "MIT",
"private": true,
Expand All @@ -9,7 +9,7 @@
"build": "rimraf dist && eslint --fix -c .eslintrc.js --ext .ts . && tsc"
},
"dependencies": {
"@mgilangjanuar/telegram": "2.2.1",
"@mgilangjanuar/telegram": "2.2.3-var1",
"@sentry/node": "^6.14.1",
"@sentry/tracing": "^6.14.1",
"@types/moment": "^2.13.0",
Expand Down
157 changes: 146 additions & 11 deletions server/src/api/v1/Auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { sign, verify } from 'jsonwebtoken'
import { getRepository } from 'typeorm'
import { Users } from '../../model//entities/Users'
import { Files } from '../../model/entities/Files'
import { COOKIE_AGE, TG_CREDS } from '../../utils/Constant'
import { CONNECTION_RETRIES, COOKIE_AGE, TG_CREDS } from '../../utils/Constant'
import { Endpoint } from '../base/Endpoint'
import { TGClient } from '../middlewares/TGClient'
import { TGSessionAuth } from '../middlewares/TGSessionAuth'
Expand Down Expand Up @@ -153,7 +153,7 @@ export class Auth {
}

/**
* Experimental
* Initialize export login token to be a param for URL tg://login?token={{token}}
* @param req
* @param res
* @returns
Expand All @@ -165,26 +165,161 @@ export class Auth {
...TG_CREDS,
exceptIds: []
}))
return res.cookie('authorization', `Bearer ${req.tg.session.save()}`).send({ token: Buffer.from(data['token']).toString('base64') })

const session = req.tg.session.save()
const auth = {
accessToken: sign({ session }, process.env.API_JWT_SECRET, { expiresIn: '15h' }),
refreshToken: sign({ session }, process.env.API_JWT_SECRET, { expiresIn: '100y' }),
expiredAfter: Date.now() + COOKIE_AGE
}
return res
.cookie('authorization', `Bearer ${auth.accessToken}`, { maxAge: COOKIE_AGE, expires: new Date(auth.expiredAfter) })
.cookie('refreshToken', auth.refreshToken, { maxAge: 3.154e+10, expires: new Date(Date.now() + 3.154e+10) })
.send({ loginToken: Buffer.from(data['token'], 'utf8').toString('base64url'), accessToken: auth.accessToken })
}

/**
* Experimental
* Sign in process with QR Code https://core.telegram.org/api/qr-login
* @param req
* @param res
* @returns
*/
@Endpoint.POST({ middlewares: [TGSessionAuth] })
public async qrCodeSignIn(req: Request, res: Response): Promise<any> {
const { token } = req.body
if (!token) {
throw { status: 400, body: { error: 'Token is required' } }
const { password, session: sessionString } = req.body

// handle the 2fa password in the second call
if (password && sessionString) {
req.tg = new TelegramClient(new StringSession(sessionString), TG_CREDS.apiId, TG_CREDS.apiHash, { connectionRetries: CONNECTION_RETRIES, useWSS: false })
await req.tg.connect()

const passwordData = await req.tg.invoke(new Api.account.GetPassword())

passwordData.newAlgo['salt1'] = Buffer.concat([passwordData.newAlgo['salt1'], generateRandomBytes(32)])
const signIn = await req.tg.invoke(new Api.auth.CheckPassword({
password: await computeCheck(passwordData, password)
}))
const userAuth = signIn['user']
if (!userAuth) {
throw { status: 400, body: { error: 'User not found/authorized' } }
}

let user = await Users.findOne({ tg_id: userAuth.id.toString() })
if (!user) {
const username = userAuth.username || userAuth.phone
user = await getRepository<Users>(Users).save({
username,
name: `${userAuth.firstName || ''} ${userAuth.lastName || ''}`.trim() || username,
tg_id: userAuth.id.toString()
}, { reload: true })
}

const session = req.tg.session.save()
const auth = {
accessToken: sign({ session }, process.env.API_JWT_SECRET, { expiresIn: '15h' }),
refreshToken: sign({ session }, process.env.API_JWT_SECRET, { expiresIn: '1y' }),
expiredAfter: Date.now() + COOKIE_AGE
}

res
.cookie('authorization', `Bearer ${auth.accessToken}`, { maxAge: COOKIE_AGE, expires: new Date(auth.expiredAfter) })
.cookie('refreshToken', auth.refreshToken, { maxAge: 3.154e+10, expires: new Date(Date.now() + 3.154e+10) })
.send({ user, ...auth })

// sync all shared files in background, if any
Files.createQueryBuilder('files')
.where('user_id = :user_id and signed_key is not null', { user_id: user.id })
.getMany()
.then(files => files?.map(file => {
const signedKey = AES.encrypt(JSON.stringify({ file: { id: file.id }, session: req.tg.session.save() }), process.env.FILES_JWT_SECRET).toString()
Files.update(file.id, { signed_key: signedKey })
}))
return
}

// handle the second call for export login token, result case: success, need to migrate to other dc, or 2fa
await req.tg.connect()
const data = await req.tg.invoke(new Api.auth.AcceptLoginToken({
token: Buffer.from(token, 'base64')
}))
return res.cookie('authorization', `Bearer ${req.tg.session.save()}`).send({ data })
try {
const data = await req.tg.invoke(new Api.auth.ExportLoginToken({
...TG_CREDS,
exceptIds: []
}))

// build response with user data and auth data
const buildResponse = (data: Record<string, any> & { user?: { id: string } })=> {
const session = req.tg.session.save()
const auth = {
accessToken: sign({ session }, process.env.API_JWT_SECRET, { expiresIn: '15h' }),
refreshToken: sign({ session }, process.env.API_JWT_SECRET, { expiresIn: '1y' }),
expiredAfter: Date.now() + COOKIE_AGE
}
res
.cookie('authorization', `Bearer ${auth.accessToken}`, { maxAge: COOKIE_AGE, expires: new Date(auth.expiredAfter) })
.cookie('refreshToken', auth.refreshToken, { maxAge: 3.154e+10, expires: new Date(Date.now() + 3.154e+10) })
.send({ ...data, ...auth })

if (data.user?.id) {
// sync all shared files in background, if any
Files.createQueryBuilder('files')
.where('user_id = :user_id and signed_key is not null', { user_id: data.user.id })
.getMany()
.then(files => files?.map(file => {
const signedKey = AES.encrypt(JSON.stringify({ file: { id: file.id }, session: req.tg.session.save() }), process.env.FILES_JWT_SECRET).toString()
Files.update(file.id, { signed_key: signedKey })
}))
}
return
}

// handle to switch dc
if (data instanceof Api.auth.LoginTokenMigrateTo) {
await req.tg._switchDC(data.dcId)
const result = await req.tg.invoke(new Api.auth.ImportLoginToken({
token: data.token
}))

// result import login token success
if (result instanceof Api.auth.LoginTokenSuccess && result.authorization instanceof Api.auth.Authorization) {
const userAuth = result.authorization.user
let user = await Users.findOne({ tg_id: userAuth.id.toString() })
if (!user) {
const username = userAuth['username'] || userAuth['phone']
user = await getRepository<Users>(Users).save({
username,
name: `${userAuth['firstName'] || ''} ${userAuth['lastName'] || ''}`.trim() || username,
tg_id: userAuth.id.toString()
}, { reload: true })
}
return buildResponse({ user })
}
return buildResponse({ data, result })

// handle if success
} else if (data instanceof Api.auth.LoginTokenSuccess && data.authorization instanceof Api.auth.Authorization) {
const userAuth = data.authorization.user
let user = await Users.findOne({ tg_id: userAuth.id.toString() })
if (!user) {
const username = userAuth['username'] || userAuth['phone']
user = await getRepository<Users>(Users).save({
username,
name: `${userAuth['firstName'] || ''} ${userAuth['lastName'] || ''}`.trim() || username,
tg_id: userAuth.id.toString()
}, { reload: true })
}
return buildResponse({ user })
}

// data instanceof auth.LoginToken
return buildResponse({
loginToken: Buffer.from(data['token'], 'utf8').toString('base64url')
})
} catch (error) {
// handle if need 2fa password
if (error.errorMessage === 'SESSION_PASSWORD_NEEDED') {
error.session = req.tg.session.save()
}
throw error
}
}

@Endpoint.GET({ middlewares: [TGSessionAuth] })
Expand Down
4 changes: 2 additions & 2 deletions web/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "web",
"version": "1.3.1",
"version": "1.4.0",
"private": true,
"dependencies": {
"@craco/craco": "^6.3.0",
Expand All @@ -25,7 +25,7 @@
"react-markdown": "^7.0.1",
"react-otp-input": "^2.4.0",
"react-paypal-button-v2": "^2.6.3",
"react-qr-code": "^2.0.2",
"react-qr-code": "^2.0.3",
"react-router-dom": "^5.3.0",
"react-scripts": "^4.0.3",
"react-twitter-widgets": "^1.10.0",
Expand Down
4 changes: 3 additions & 1 deletion web/public/app.dark.css
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
color: rgba(255, 255, 255, 0.45) !important;
}

.ant-modal-body .ant-form-item-label {
.ant-modal-body .ant-form-item-label,
.ant-modal-body .ant-form-item-control-input,
.ant-modal-body .ant-row {
background: #1f1f1f !important;
}

Expand Down
4 changes: 1 addition & 3 deletions web/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ ReactDOM.render(
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://cra.link/PWA
serviceWorkerRegistration.register({
onUpdate: () => (window.location as any).reload(true)
})
serviceWorkerRegistration.register()

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
Expand Down
Loading

0 comments on commit a96dd00

Please sign in to comment.