Skip to content
Merged
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
26 changes: 15 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"@notionhq/client": "^2.2.3",
"@sentry/node": "^7.50.0",
"@temporalio/client": "^1.11.3",
"@togethercrew.dev/db": "^3.0.72",
"@togethercrew.dev/db": "^3.0.78",
"@togethercrew.dev/tc-messagebroker": "^0.0.50",
"@types/express-session": "^1.17.7",
"@types/morgan": "^1.9.5",
Expand Down
4 changes: 4 additions & 0 deletions src/config/telegram.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const verificationToken = {
verificationCodeLength: 5,
allowedCharacters: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
};
6 changes: 6 additions & 0 deletions src/controllers/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,15 @@ const refreshTokens = catchAsync(async function (req: Request, res: Response) {
res.send({ ...tokens });
});

const generateToken = catchAsync(async function (req: Request, res: Response) {
const token = await tokenService.generateTelegramVerificationToken(req.user.id, req.body.communityId);
res.send(token);
});

export default {
discordAuthorize,
discordAuthorizeCallback,
refreshTokens,
logout,
generateToken,
};
77 changes: 61 additions & 16 deletions src/docs/auth.doc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ paths:
summary: Discord OAuth2 Authorization
description: Redirects the user to the Discord OAuth2 authorization page.
responses:
"302":
'302':
description: Found (Redirect to Discord OAuth2 page)
$ref: "#/components/responses/Found"
$ref: '#/components/responses/Found'

/api/v1/auth/refresh-tokens:
post:
Expand All @@ -28,7 +28,7 @@ paths:
required:
- refreshToken
responses:
"200":
'200':
description: OK
content:
application/json:
Expand All @@ -39,16 +39,16 @@ paths:
type: object
properties:
access:
$ref: "#/components/schemas/Token"
$ref: '#/components/schemas/Token'
refresh:
$ref: "#/components/schemas/Token"
"400":
$ref: '#/components/schemas/Token'
'400':
description: Bad Request
$ref: "#/components/responses/BadRequest"
"401":
$ref: '#/components/responses/BadRequest'
'401':
description: Unauthorized
$ref: "#/components/responses/Unauthorized"
$ref: '#/components/responses/Unauthorized'

/api/v1/auth/logout:
post:
tags:
Expand All @@ -67,12 +67,57 @@ paths:
required:
- refreshToken
responses:
"204":
'204':
description: No Content (Successfully logged out)
$ref: "#/components/responses/NoContent"
"400":
$ref: '#/components/responses/NoContent'
'400':
description: Bad Request
$ref: "#/components/responses/BadRequest"
"404":
$ref: '#/components/responses/BadRequest'
'404':
description: Not found (Invalid refresh token)
$ref: "#/components/responses/NotFound"
$ref: '#/components/responses/NotFound'

/api/v1/auth/generate-token:
post:
tags:
- Auth
summary: Generate token for verfication purpose.
description: Generate token for verfication purpose (telegram_verification) with specific requirements based on the token type.
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
type:
type: string
enum: ['telegram_verification']
required: true
communityId:
type: string
format: objectId
description: The communityId, required for telegram_verification.
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
value:
type: string
expiresAt:
type: date
example:
value: 'JK1NE'
expiresAt: '2024-12-20T07:28:57.718Z'
'400':
description: Bad Request
$ref: '#/components/responses/BadRequest'
'401':
description: Unauthorized
$ref: '#/components/responses/Unauthorized'
7 changes: 7 additions & 0 deletions src/docs/platform.doc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ paths:
- notion
- mediaWiki
- discourse
- telegram
description: Name of the platform to create. Must be one of the supported platforms.
community:
type: string
Expand Down Expand Up @@ -188,6 +189,12 @@ paths:
isInProgress:
type: boolean
description: Metadata for Discourse.
- type: object
required: [chat]
properties:
chat:
type: object
description: Metadata for Telegram.
responses:
'201':
description: Platform created successfully.
Expand Down
3 changes: 2 additions & 1 deletion src/routes/v1/auth.route.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import express from 'express';
import { authController } from '../../controllers';
import { authValidation } from '../../validations';
import { validate } from '../../middlewares';
import { validate, auth } from '../../middlewares';
const router = express.Router();

// Routes
router.get('/discord/authorize', authController.discordAuthorize);
router.get('/discord/authorize/callback', authController.discordAuthorizeCallback);
router.post('/generate-token', auth(), validate(authValidation.generateToken), authController.generateToken);
router.post('/logout', validate(authValidation.logout), authController.logout);
router.post('/refresh-tokens', validate(authValidation.refreshTokens), authController.refreshTokens);

Expand Down
39 changes: 39 additions & 0 deletions src/services/token.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { IToken, Token, IUser } from '@togethercrew.dev/db';
import { IDiscordOAuth2EchangeCode, IAuthTokens } from 'src/interfaces';
import { Auth } from 'googleapis';
import discordServices from './discord';
import crypto from 'crypto';
import { verificationToken } from '../config/telegram';
/**
* Generate token
* @param {IUser} user
Expand Down Expand Up @@ -42,13 +44,15 @@ async function saveToken(
expires: moment.Moment,
type: string,
blacklisted = false,
community?: Types.ObjectId,
): Promise<IToken> {
const tokenDoc: IToken = await Token.create({
token,
user: userId,
expires: expires.toDate(),
type,
blacklisted,
community,
});
return tokenDoc;
}
Expand Down Expand Up @@ -187,6 +191,40 @@ async function saveNotionAccessToken(userId: Types.ObjectId, accessToken: string
await saveToken(accessToken, userId, accessTokenExpires, TokenTypeNames.NOTION_ACCESS);
}

/**
* Generate a random verification code using crypto
* @param {number} length - Length of the code
* @param {string} allowedCharacters - Characters to choose from
* @returns {string}
*/
function generateRandomCode(length: number, allowedCharacters: string): string {
const charsLength = allowedCharacters.length;
const randomBytes = crypto.randomBytes(length);
let code = '';
for (let i = 0; i < length; i++) {
const randomIndex = randomBytes[i] % charsLength;
code += allowedCharacters.charAt(randomIndex);
}
return code;
}

/**
* Generate a Telegram verification token
* @param {Types.ObjectId} userId
* @param {Types.ObjectId} communityId
* @returns {Promise<{ value: string; expiresAt: Date }>}
*/
async function generateTelegramVerificationToken(userId: Types.ObjectId, communityId: Types.ObjectId) {
const { verificationCodeLength, allowedCharacters } = verificationToken;

const value = generateRandomCode(verificationCodeLength, allowedCharacters);
const expiresAt = moment().add(10, 'minutes');

await saveToken(value, userId, expiresAt, TokenTypeNames.TELEGRAM_VERIFICATION, false, communityId);

return { value, expiresAt: expiresAt.toDate() };
}

export default {
generateToken,
verifyToken,
Expand All @@ -196,4 +234,5 @@ export default {
saveDiscordOAuth2Tokens,
saveGoogleOAuth2Tokens,
saveNotionAccessToken,
generateTelegramVerificationToken,
};
15 changes: 15 additions & 0 deletions src/validations/auth.validation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import Joi from 'joi';
import { objectId } from './custom.validation';
import { TokenTypeNames } from '@togethercrew.dev/db';

const logout = {
body: Joi.object().required().keys({
Expand All @@ -11,8 +13,21 @@ const refreshTokens = {
refreshToken: Joi.string().required(),
}),
};
const generateToken = {
body: Joi.object().keys({
type: Joi.string().required().valid(TokenTypeNames.TELEGRAM_VERIFICATION, TokenTypeNames.ACCESS),
communityId: Joi.string()
.custom(objectId)
.when('type', {
is: Joi.string().valid(TokenTypeNames.TELEGRAM_VERIFICATION),
then: Joi.required(),
otherwise: Joi.forbidden(),
}),
}),
};

export default {
logout,
refreshTokens,
generateToken,
};
12 changes: 12 additions & 0 deletions src/validations/platform.validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ const discordCreateMetadata = () => {
});
};

const telegramMetadata = () => {
return Joi.object()
.required()
.keys({
chat: Joi.object().unknown(true).required(),
});
};

const discordUpdateMetadata = () => {
return Joi.object().keys({
selectedChannels: Joi.array().items(Joi.string()),
Expand Down Expand Up @@ -120,6 +128,10 @@ const createPlatform = {
is: PlatformNames.Discourse,
then: discourseMetadata(),
},
{
is: PlatformNames.Telegram,
then: telegramMetadata,
},
],
}).required(),
}),
Expand Down
Loading