Starting on discord, switching to remote env
This commit is contained in:
parent
1b7715d0c8
commit
e9e5e8f7ec
10 changed files with 130 additions and 34 deletions
10
README.md
10
README.md
|
@ -20,11 +20,19 @@ Very unfinished!! Mostly a programming exercise for me for now to practice fulls
|
||||||
(the ones that can use the website to join)
|
(the ones that can use the website to join)
|
||||||
|
|
||||||
- [Slack](#Slack)
|
- [Slack](#Slack)
|
||||||
|
- [Discord](#Discord)
|
||||||
|
|
||||||
### Slack
|
### Slack
|
||||||
|
|
||||||
https://github.com/slackapi/node-slack-sdk
|
- Set up Slack App with old style bot according to matterbridge instructions: https://github.com/42wim/matterbridge/wiki/Slack-bot-setup
|
||||||
|
- Configure slack app to use the callback API url: `<yourdomain.etc.>/api/slack/oauth_redirect`
|
||||||
|
- Set your `.env` variables!
|
||||||
|
|
||||||
|
### Discord
|
||||||
|
|
||||||
|
- Set up Discord App according to matterbridge instructions: https://github.com/42wim/matterbridge/wiki/Discord-bot-setup
|
||||||
|
- Additionally enable "[Requires OAUTH2 Code Grant](https://discord.com/developers/docs/topics/oauth2#advanced-bot-authorization)" in your bot's authorization flow settings
|
||||||
|
- Set the redirect URL to `<yourdomain.etc>/api/discord/oauth_redirect`
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
|
|
0
client/src/components/platforms/discordLogin.tsx
Normal file
0
client/src/components/platforms/discordLogin.tsx
Normal file
21
example.env
21
example.env
|
@ -9,11 +9,6 @@ POSTGRES_USER=
|
||||||
POSTGRES_PASSWORD=
|
POSTGRES_PASSWORD=
|
||||||
POSTGRES_DB=
|
POSTGRES_DB=
|
||||||
|
|
||||||
SLACK_CLIENT_ID=
|
|
||||||
SLACK_CLIENT_SECRET=
|
|
||||||
SLACK_SIGNING_SECRET=
|
|
||||||
SLACK_STATE_SECRET=
|
|
||||||
|
|
||||||
# Used to log in to create and manage groups
|
# Used to log in to create and manage groups
|
||||||
ADMIN_TOKEN=
|
ADMIN_TOKEN=
|
||||||
|
|
||||||
|
@ -24,3 +19,19 @@ COOKIE_KEY_2=
|
||||||
# Location of the matterbridge binary!
|
# Location of the matterbridge binary!
|
||||||
MATTERBRIDGE_BINARY=
|
MATTERBRIDGE_BINARY=
|
||||||
MATTERBRIDGE_CONFIG_DIR=
|
MATTERBRIDGE_CONFIG_DIR=
|
||||||
|
|
||||||
|
# ---------------
|
||||||
|
## Platforms
|
||||||
|
# ---------------
|
||||||
|
|
||||||
|
# Slack -----------------
|
||||||
|
SLACK_CLIENT_ID=
|
||||||
|
SLACK_CLIENT_SECRET=
|
||||||
|
SLACK_SIGNING_SECRET=
|
||||||
|
SLACK_STATE_SECRET=
|
||||||
|
|
||||||
|
# Discord ----------------
|
||||||
|
DISCORD_TOKEN=
|
||||||
|
DISCORD_CLIENT_ID=
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,10 @@ export default {
|
||||||
signing_secret: 'SLACK_SIGNING_SECRET',
|
signing_secret: 'SLACK_SIGNING_SECRET',
|
||||||
state_secret: 'SLACK_STATE_SECRET'
|
state_secret: 'SLACK_STATE_SECRET'
|
||||||
},
|
},
|
||||||
|
discordConfig: {
|
||||||
|
token: 'DISCORD_TOKEN',
|
||||||
|
client_id: "DISCORD_CLIENT_ID"
|
||||||
|
},
|
||||||
admin_token: 'ADMIN_TOKEN',
|
admin_token: 'ADMIN_TOKEN',
|
||||||
cookies:{
|
cookies:{
|
||||||
key1: 'COOKIE_KEY_1',
|
key1: 'COOKIE_KEY_1',
|
||||||
|
|
|
@ -13,6 +13,7 @@ import slackRoutes from "./routes/slack.routes";
|
||||||
import authRoutes from "./routes/auth.routes";
|
import authRoutes from "./routes/auth.routes";
|
||||||
import bridgeRoutes from './routes/bridge.routes';
|
import bridgeRoutes from './routes/bridge.routes';
|
||||||
import channelRoutes from './routes/channel.routes';
|
import channelRoutes from './routes/channel.routes';
|
||||||
|
import discordRoutes from "./routes/discord.routes";
|
||||||
|
|
||||||
import logger from "./logging";
|
import logger from "./logging";
|
||||||
|
|
||||||
|
@ -39,6 +40,7 @@ AppDataSource.initialize()
|
||||||
app.use('/slack', slackRoutes);
|
app.use('/slack', slackRoutes);
|
||||||
app.use('/bridge', bridgeRoutes);
|
app.use('/bridge', bridgeRoutes);
|
||||||
app.use('/channel', channelRoutes);
|
app.use('/channel', channelRoutes);
|
||||||
|
app.use('/discord', discordRoutes);
|
||||||
|
|
||||||
app.all('*', (req: Request, res: Response, next: NextFunction) => {
|
app.all('*', (req: Request, res: Response, next: NextFunction) => {
|
||||||
next(new AppError(404, `Route ${req.originalUrl} not found`));
|
next(new AppError(404, `Route ${req.originalUrl} not found`));
|
||||||
|
|
32
server/src/controllers/discord.controller.ts
Normal file
32
server/src/controllers/discord.controller.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
|
||||||
|
import {Request, Response} from "express";
|
||||||
|
import {randomUUID} from "crypto";
|
||||||
|
import config from "config";
|
||||||
|
import {discordConfigType} from "../types/config";
|
||||||
|
import {DiscordBridge} from "../entities/bridge.entity";
|
||||||
|
import {AppDataSource} from "../db/data-source";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const discordBridgeRepository = AppDataSource.getRepository(DiscordBridge)
|
||||||
|
const discordConfig = config.get<discordConfigType>('discordConfig');
|
||||||
|
|
||||||
|
export const DiscordInstallLinkHandler = async(
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
) => {
|
||||||
|
let state_token = randomUUID()
|
||||||
|
req.session.state_token = state_token;
|
||||||
|
|
||||||
|
const url = `https://discordapp.com/oauth2/authorize?&client_id=${discordConfig.client_id}&scope=bot&permissions=536870912&state=${state_token}`
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const DiscordOAuthHandler = async(
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
) => {
|
||||||
|
console.log('discord oauth', req.body, req.query)
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import config from 'config';
|
import config from 'config';
|
||||||
import {Request, Response} from 'express';
|
import {Request, Response} from 'express';
|
||||||
import {AppDataSource} from "../db/data-source";
|
import {AppDataSource} from "../db/data-source";
|
||||||
import {Bridge} from "../entities/bridge.entity";
|
import {Bridge, SlackBridge} from "../entities/bridge.entity";
|
||||||
import {Group} from "../entities/group.entity";
|
import {Group} from "../entities/group.entity";
|
||||||
import {JoinSlackChannelInput} from "../schemas/slack.schema";
|
import {JoinSlackChannelInput} from "../schemas/slack.schema";
|
||||||
import {randomUUID} from "crypto";
|
import {randomUUID} from "crypto";
|
||||||
|
@ -12,7 +12,7 @@ import { slackConfigType } from "../types/config";
|
||||||
const { InstallProvider, LogLevel, FileInstallationStore } = require('@slack/oauth');
|
const { InstallProvider, LogLevel, FileInstallationStore } = require('@slack/oauth');
|
||||||
|
|
||||||
const scopes = ['bot', 'channels:write', 'channels:write.invites', 'chat:write:bot', 'chat:write:user', 'users.profile:read'];
|
const scopes = ['bot', 'channels:write', 'channels:write.invites', 'chat:write:bot', 'chat:write:user', 'users.profile:read'];
|
||||||
const bridgeRepository = AppDataSource.getRepository(Bridge)
|
const slackBridgeRepository = AppDataSource.getRepository(SlackBridge)
|
||||||
const groupRepository = AppDataSource.getRepository(Group)
|
const groupRepository = AppDataSource.getRepository(Group)
|
||||||
|
|
||||||
const SLACK_COOKIE_NAME = "slack-oauth-state";
|
const SLACK_COOKIE_NAME = "slack-oauth-state";
|
||||||
|
@ -77,10 +77,10 @@ export const SlackCallbackHandler = async(
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if we have an entity
|
// check if we have an entity
|
||||||
let bridge = await bridgeRepository.findOneBy({Token: installation.bot.token})
|
let bridge = await slackBridgeRepository.findOneBy({Token: installation.bot.token})
|
||||||
if (!bridge){
|
if (!bridge){
|
||||||
bridge = await bridgeRepository.create(bridge_data);
|
bridge = await slackBridgeRepository.create(bridge_data);
|
||||||
await bridgeRepository.save(bridge);
|
await slackBridgeRepository.save(bridge);
|
||||||
logger.debug('created bridge')
|
logger.debug('created bridge')
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ export const SlackCallbackHandler = async(
|
||||||
// Don't overwrite existing label
|
// Don't overwrite existing label
|
||||||
bridge_data.Label = bridge.Label
|
bridge_data.Label = bridge.Label
|
||||||
bridge_data = {...bridge, ...bridge_data}
|
bridge_data = {...bridge, ...bridge_data}
|
||||||
let newbridge = await bridgeRepository.save(bridge_data)
|
let newbridge = await slackBridgeRepository.save(bridge_data)
|
||||||
}
|
}
|
||||||
res.send('<html><body><h1>Success! Return to the chatbridge login window.</h1><h3>This tab will close in 3 seconds...</h3><script>window.setTimeout(window.close, 3000)</script></body>')
|
res.send('<html><body><h1>Success! Return to the chatbridge login window.</h1><h3>This tab will close in 3 seconds...</h3><script>window.setTimeout(window.close, 3000)</script></body>')
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ export const getChannelsHandler = async(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response
|
res: Response
|
||||||
) => {
|
) => {
|
||||||
let bridge = await bridgeRepository.findOneBy({state_token: req.session.state_token})
|
let bridge = await slackBridgeRepository.findOneBy({state_token: req.session.state_token})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fetch('https://slack.com/api/conversations.list', {
|
fetch('https://slack.com/api/conversations.list', {
|
||||||
|
@ -150,7 +150,7 @@ export const joinChannelsHandler = async(
|
||||||
res: Response
|
res: Response
|
||||||
) => {
|
) => {
|
||||||
|
|
||||||
let bridge = await bridgeRepository.findOneBy({state_token: req.session.state_token})
|
let bridge = await slackBridgeRepository.findOneBy({state_token: req.session.state_token})
|
||||||
|
|
||||||
fetch('https://slack.com/api/conversations.invite', {
|
fetch('https://slack.com/api/conversations.invite', {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
@ -184,7 +184,7 @@ export const getBotInfo = async(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response
|
res: Response
|
||||||
) => {
|
) => {
|
||||||
let bridge = await bridgeRepository.findOneBy({state_token: req.session.state_token})
|
let bridge = await slackBridgeRepository.findOneBy({state_token: req.session.state_token})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fetch('https://slack.com/api/auth.test', {
|
fetch('https://slack.com/api/auth.test', {
|
||||||
|
|
|
@ -20,13 +20,6 @@ export class Bridge extends Model {
|
||||||
@Column()
|
@Column()
|
||||||
Label: string;
|
Label: string;
|
||||||
|
|
||||||
// The ID of the team
|
|
||||||
@Column({nullable:true})
|
|
||||||
team_id: string;
|
|
||||||
|
|
||||||
@Column({nullable:true})
|
|
||||||
team_name: string;
|
|
||||||
|
|
||||||
// Used to fetch the bridge data from the client while installing
|
// Used to fetch the bridge data from the client while installing
|
||||||
@Column()
|
@Column()
|
||||||
state_token: string;
|
state_token: string;
|
||||||
|
@ -37,16 +30,6 @@ export class Bridge extends Model {
|
||||||
})
|
})
|
||||||
Token: string;
|
Token: string;
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: true
|
|
||||||
})
|
|
||||||
user_token: string;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable:true
|
|
||||||
})
|
|
||||||
bot_id: string;
|
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
default: true
|
default: true
|
||||||
})
|
})
|
||||||
|
@ -58,8 +41,7 @@ export class Bridge extends Model {
|
||||||
})
|
})
|
||||||
RemoteNickFormat: string;
|
RemoteNickFormat: string;
|
||||||
|
|
||||||
|
// Bridged channels (not the channels in the slack/discord/etc.)
|
||||||
|
|
||||||
@OneToMany(() => Channel, (channel) => channel.bridge,
|
@OneToMany(() => Channel, (channel) => channel.bridge,
|
||||||
{
|
{
|
||||||
cascade: ["remove"]
|
cascade: ["remove"]
|
||||||
|
@ -68,6 +50,38 @@ export class Bridge extends Model {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ChildEntity()
|
||||||
|
export class SlackBridge extends Bridge {
|
||||||
|
// The ID of the team
|
||||||
|
@Column({nullable:true})
|
||||||
|
team_id: string;
|
||||||
|
|
||||||
|
@Column({nullable:true})
|
||||||
|
team_name: string;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
nullable: true
|
||||||
|
})
|
||||||
|
user_token: string;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
nullable:true
|
||||||
|
})
|
||||||
|
bot_id: string;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@ChildEntity()
|
||||||
|
export class DiscordBridge extends Bridge {
|
||||||
|
// Server name - needed to use multiple 'servers' with a single discord app
|
||||||
|
@Column()
|
||||||
|
Server: string;
|
||||||
|
|
||||||
|
// Analogous to team_id in slack bridge
|
||||||
|
@Column()
|
||||||
|
guild_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
@ChildEntity()
|
@ChildEntity()
|
||||||
export class MatrixBridge extends Bridge {
|
export class MatrixBridge extends Bridge {
|
||||||
// Matrix-specific
|
// Matrix-specific
|
||||||
|
|
20
server/src/routes/discord.routes.ts
Normal file
20
server/src/routes/discord.routes.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import express from 'express';
|
||||||
|
|
||||||
|
import {
|
||||||
|
DiscordInstallLinkHandler,
|
||||||
|
DiscordOAuthHandler
|
||||||
|
} from "../controllers/discord.controller";
|
||||||
|
|
||||||
|
import {
|
||||||
|
requireStateToken
|
||||||
|
} from "../middleware/cookies";
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
router.route('/install')
|
||||||
|
.get(DiscordInstallLinkHandler)
|
||||||
|
|
||||||
|
router.route('/oauth_redirect')
|
||||||
|
.get(DiscordOAuthHandler)
|
||||||
|
|
||||||
|
|
||||||
|
export default router
|
|
@ -4,3 +4,8 @@ export interface slackConfigType {
|
||||||
signing_secret: string,
|
signing_secret: string,
|
||||||
state_secret: string
|
state_secret: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface discordConfigType {
|
||||||
|
token: string;
|
||||||
|
client_id: string;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue