mirror of
https://github.com/roleypoly/roleypoly-v1.git
synced 2025-06-17 02:29:10 +00:00
lerna: split up bulk of packages
This commit is contained in:
parent
cb0b1d2410
commit
47a2e5694e
90 changed files with 0 additions and 0 deletions
171
packages/roleypoly-server/api/auth.js
Normal file
171
packages/roleypoly-server/api/auth.js
Normal file
|
@ -0,0 +1,171 @@
|
|||
// @flow
|
||||
import { type Context } from 'koa'
|
||||
import { type AppContext, type Router } from '../Roleypoly'
|
||||
import ksuid from 'ksuid'
|
||||
import logger from '../logger'
|
||||
import renderError from '../util/error'
|
||||
const log = logger(__filename)
|
||||
|
||||
export default (R: Router, $: AppContext) => {
|
||||
R.post('/api/auth/token', async (ctx: Context) => {
|
||||
const { token } = ((ctx.request.body: any): { token: string })
|
||||
|
||||
if (token == null || token === '') {
|
||||
ctx.body = { err: 'token_missing' }
|
||||
ctx.status = 400
|
||||
return
|
||||
}
|
||||
|
||||
console.log(ctx.session.expiresAt >= new Date(), ctx.session.expiresAt, new Date())
|
||||
|
||||
if (ctx.session.accessToken === undefined || ctx.session.expiresAt >= new Date()) {
|
||||
const data = await $.discord.initializeOAuth(token)
|
||||
ctx.session.accessToken = data.access_token
|
||||
ctx.session.refreshToken = data.refresh_token
|
||||
ctx.session.expiresAt = new Date() + ctx.expires_in
|
||||
}
|
||||
|
||||
const user = await $.discord.getUserFromToken(ctx.session.accessToken)
|
||||
ctx.session.userId = user.id
|
||||
ctx.session.avatarHash = user.avatar
|
||||
|
||||
ctx.body = {
|
||||
id: user.id,
|
||||
avatar: user.avatar,
|
||||
username: user.username,
|
||||
discriminator: user.discriminator
|
||||
}
|
||||
})
|
||||
|
||||
R.get('/api/auth/user', async (ctx: Context) => {
|
||||
const { accessToken } = ((ctx.session: any): { accessToken?: string })
|
||||
if (accessToken === undefined) {
|
||||
ctx.body = { err: 'not_logged_in' }
|
||||
ctx.status = 401
|
||||
return
|
||||
}
|
||||
|
||||
const user = await $.discord.getUserFromToken(accessToken)
|
||||
ctx.session.userId = user.id
|
||||
ctx.session.avatarHash = user.avatar
|
||||
|
||||
ctx.body = {
|
||||
id: user.id,
|
||||
avatar: user.avatar,
|
||||
username: user.username,
|
||||
discriminator: user.discriminator
|
||||
}
|
||||
})
|
||||
|
||||
R.get('/api/auth/redirect', async (ctx: Context) => {
|
||||
const { r } = ctx.query
|
||||
// check if already authed
|
||||
if (await $.auth.isLoggedIn(ctx, { refresh: true })) {
|
||||
log.debug('already authed.', ctx.session)
|
||||
return ctx.redirect(r || '/')
|
||||
}
|
||||
|
||||
ctx.session.oauthRedirect = r
|
||||
const url = $.discord.getAuthUrl(ksuid.randomSync().string)
|
||||
if (ctx.query.url === '✔️') {
|
||||
ctx.body = { url }
|
||||
return
|
||||
}
|
||||
|
||||
ctx.redirect(url)
|
||||
})
|
||||
|
||||
R.get('/api/oauth/callback', async (ctx: Context, next: *) => {
|
||||
const { code, state } = ctx.query
|
||||
const { oauthRedirect: r } = ctx.session
|
||||
delete ctx.session.oauthRedirect
|
||||
if (await $.auth.isLoggedIn(ctx)) {
|
||||
log.debug('user was logged in')
|
||||
return ctx.redirect(r || '/')
|
||||
}
|
||||
|
||||
if (code == null) {
|
||||
ctx.status = 400
|
||||
await renderError($, ctx)
|
||||
return
|
||||
}
|
||||
|
||||
if (state != null) {
|
||||
try {
|
||||
const ksState = ksuid.parse(state)
|
||||
const fiveMinAgo = new Date() - 1000 * 60 * 5
|
||||
if (ksState.date < fiveMinAgo) {
|
||||
ctx.status = 419
|
||||
await renderError($, ctx)
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
ctx.status = 400
|
||||
await renderError($, ctx)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const tokens = await $.discord.initializeOAuth(code)
|
||||
const user = await $.discord.getUserFromToken(tokens.access_token)
|
||||
$.auth.injectSessionFromOAuth(ctx, tokens, user.id)
|
||||
log.debug('user logged in', { tokens, user, s: ctx.session })
|
||||
return ctx.redirect(r || '/')
|
||||
} catch (e) {
|
||||
log.error('token and auth fetch failure', e)
|
||||
ctx.status = 400
|
||||
return renderError($, ctx)
|
||||
}
|
||||
})
|
||||
|
||||
R.post('/api/auth/logout', async (ctx: Context) => {
|
||||
ctx.session = null
|
||||
})
|
||||
|
||||
R.get('/api/auth/logout', async (ctx: Context) => {
|
||||
if (await $.auth.isLoggedIn(ctx)) {
|
||||
if (ctx.session.authType === 'oauth') {
|
||||
await $.discord.revokeOAuth(ctx.session)
|
||||
}
|
||||
}
|
||||
|
||||
ctx.session = null
|
||||
return ctx.redirect('/')
|
||||
})
|
||||
|
||||
R.get('/api/oauth/bot', async (ctx: Context) => {
|
||||
const url = $.discord.getBotJoinUrl()
|
||||
if (ctx.query.url === '✔️') {
|
||||
ctx.body = { url }
|
||||
return
|
||||
}
|
||||
|
||||
ctx.redirect(url)
|
||||
})
|
||||
|
||||
R.get('/api/oauth/bot/callback', async (ctx: Context) => {
|
||||
// console.log(ctx.request)
|
||||
})
|
||||
|
||||
R.get('/magic/:challenge', async (ctx: Context) => {
|
||||
if (ctx.request.headers['user-agent'].includes('Discordbot')) {
|
||||
return $.ui.render(ctx.req, ctx.res, '/_internal/_discordbot/_magic', {})
|
||||
}
|
||||
|
||||
const { challenge } = ((ctx.params: any): { challenge: string })
|
||||
const chall = await $.auth.fetchDMChallenge({ magic: challenge })
|
||||
// log.notice('magic user agent', { ua: ctx.request.headers['User-Agent'] })
|
||||
|
||||
if (chall == null) {
|
||||
log.warn('bad magic', challenge)
|
||||
return ctx.redirect('/auth/expired')
|
||||
}
|
||||
|
||||
$.auth.injectSessionFromChallenge(ctx, chall)
|
||||
await $.auth.deleteDMChallenge(chall)
|
||||
log.info('logged in via magic', chall)
|
||||
|
||||
return ctx.redirect('/')
|
||||
})
|
||||
}
|
32
packages/roleypoly-server/api/index.js
Normal file
32
packages/roleypoly-server/api/index.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
// @flow
|
||||
import logger from '../logger'
|
||||
import glob from 'glob'
|
||||
|
||||
import type { Router, AppContext } from '../Roleypoly'
|
||||
|
||||
const log = logger(__filename)
|
||||
|
||||
const PROD = process.env.NODE_ENV === 'production'
|
||||
|
||||
export default async (router: Router, ctx: AppContext, { forceClear = false }: { forceClear: boolean } = {}) => {
|
||||
const apis = glob.sync(`./api/**/!(index).js`)
|
||||
log.debug('found apis', apis)
|
||||
|
||||
for (let a of apis) {
|
||||
if (a.endsWith('_test.js') && PROD) {
|
||||
log.debug(`skipping ${a}`)
|
||||
continue
|
||||
}
|
||||
log.debug(`mounting ${a}`)
|
||||
try {
|
||||
const pathname = a.replace('api/', '')
|
||||
if (forceClear) {
|
||||
delete require.cache[require.resolve(pathname)]
|
||||
}
|
||||
// $FlowFixMe this isn't an important error. potentially dangerous, but irrelevant.
|
||||
require(pathname).default(router, ctx)
|
||||
} catch (e) {
|
||||
log.error(`couldn't mount ${a}`, e)
|
||||
}
|
||||
}
|
||||
}
|
137
packages/roleypoly-server/api/servers.js
Normal file
137
packages/roleypoly-server/api/servers.js
Normal file
|
@ -0,0 +1,137 @@
|
|||
// @flow
|
||||
import { type Context } from 'koa'
|
||||
import { type AppContext, type Router } from '../Roleypoly'
|
||||
import { type ServerModel } from '../models/Server'
|
||||
|
||||
export default (R: Router, $: AppContext) => {
|
||||
R.get('/api/servers', async (ctx: Context) => {
|
||||
try {
|
||||
const { userId } = ctx.session
|
||||
const srv = $.discord.getRelevantServers(userId)
|
||||
const presentable = await $.P.presentableServers(srv, userId)
|
||||
|
||||
ctx.body = presentable
|
||||
} catch (e) {
|
||||
console.error(e.trace || e.stack)
|
||||
}
|
||||
})
|
||||
|
||||
R.get('/api/server/:id', async (ctx: Context) => {
|
||||
const { userId } = ctx.session
|
||||
const { id } = ctx.params
|
||||
|
||||
const srv = $.discord.client.guilds.get(id)
|
||||
|
||||
if (srv == null) {
|
||||
ctx.body = { err: 'not found' }
|
||||
ctx.status = 404
|
||||
return
|
||||
}
|
||||
|
||||
let gm
|
||||
if (srv.members.has(userId)) {
|
||||
gm = $.discord.gm(id, userId)
|
||||
} else if ($.discord.isRoot(userId)) {
|
||||
gm = $.discord.fakeGm({ id: userId })
|
||||
}
|
||||
|
||||
if (gm == null) {
|
||||
ctx.body = { err: 'not_a_member' }
|
||||
ctx.status = 400
|
||||
return
|
||||
}
|
||||
|
||||
const server = await $.P.presentableServer(srv, gm)
|
||||
|
||||
// $FlowFixMe bad koa type
|
||||
ctx.body = server
|
||||
})
|
||||
|
||||
R.get('/api/server/:id/slug', async (ctx: Context) => {
|
||||
// const { userId } = ctx.session
|
||||
const { id } = ctx.params
|
||||
|
||||
const srv = $.discord.client.guilds.get(id)
|
||||
|
||||
console.log(srv)
|
||||
|
||||
if (srv == null) {
|
||||
ctx.body = { err: 'not found' }
|
||||
ctx.status = 404
|
||||
return
|
||||
}
|
||||
|
||||
// $FlowFixMe bad koa type
|
||||
ctx.body = await $.P.serverSlug(srv)
|
||||
})
|
||||
|
||||
R.patch('/api/server/:id', async (ctx: Context) => {
|
||||
const { userId } = (ctx.session: { userId: string })
|
||||
const { id } = (ctx.params: { id: string })
|
||||
|
||||
let gm = $.discord.gm(id, userId)
|
||||
if (gm == null) {
|
||||
if ($.discord.isRoot(userId)) {
|
||||
gm = $.discord.fakeGm({ id: userId })
|
||||
} else {
|
||||
ctx.status = 403
|
||||
ctx.body = {
|
||||
err: 'not permitted'
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// check perms
|
||||
if (!$.discord.getPermissions(gm).canManageRoles) {
|
||||
ctx.status = 403
|
||||
ctx.body = { err: 'cannot_manage_roles' }
|
||||
return
|
||||
}
|
||||
|
||||
const { message, categories } = ((ctx.request.body: any): $Shape<ServerModel>)
|
||||
|
||||
// todo make less nasty
|
||||
await $.server.update(id, {
|
||||
...((message != null) ? { message } : {}),
|
||||
...((categories != null) ? { categories } : {})
|
||||
})
|
||||
|
||||
ctx.body = { ok: true }
|
||||
})
|
||||
|
||||
R.patch('/api/servers/:server/roles', async (ctx: Context) => {
|
||||
const { userId } = (ctx.session: { userId: string })
|
||||
const { server } = (ctx.params: { server: string })
|
||||
|
||||
let gm = $.discord.gm(server, userId)
|
||||
if (gm == null) {
|
||||
if ($.discord.isRoot(userId)) {
|
||||
gm = $.discord.fakeGm({ id: userId })
|
||||
} else {
|
||||
ctx.status = 403
|
||||
ctx.body = {
|
||||
err: 'not permitted'
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const originalRoles = gm.roles
|
||||
let { added, removed } = ((ctx.request.body: any): { added: string[], removed: string[] })
|
||||
|
||||
const allowedRoles: string[] = await $.server.getAllowedRoles(server)
|
||||
|
||||
const isSafe = (r: string) => $.discord.safeRole(server, r) && allowedRoles.includes(r)
|
||||
|
||||
added = added.filter(isSafe)
|
||||
removed = removed.filter(isSafe)
|
||||
|
||||
const newRoles = originalRoles.concat(added).filter(x => !removed.includes(x))
|
||||
gm.edit({
|
||||
roles: newRoles
|
||||
})
|
||||
|
||||
ctx.body = { ok: true }
|
||||
})
|
||||
}
|
28
packages/roleypoly-server/api/servers_test.js
Normal file
28
packages/roleypoly-server/api/servers_test.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
// @flow
|
||||
import { type Context } from 'koa'
|
||||
import { type AppContext, type Router } from '../Roleypoly'
|
||||
export default (R: Router, $: AppContext) => {
|
||||
R.get('/api/~/relevant-servers/:user', async (ctx: Context, next: () => void) => {
|
||||
// ctx.body = 'ok'
|
||||
const srv = $.discord.getRelevantServers(ctx.params.user)
|
||||
ctx.body = $.P.presentableServers(srv, ctx.params.user)
|
||||
})
|
||||
|
||||
// R.get('/api/~/roles/:id/:userId', (ctx, next) => {
|
||||
// // ctx.body = 'ok'
|
||||
// const { id, userId } = ctx.params
|
||||
|
||||
// const srv = $.discord.client.guilds.get(id)
|
||||
|
||||
// if (srv === undefined) {
|
||||
// ctx.body = { err: 'not found' }
|
||||
// ctx.status = 404
|
||||
// return
|
||||
// }
|
||||
|
||||
// const gm = srv.members.get(userId)
|
||||
// const roles = $.P.presentableRoles(id, gm)
|
||||
|
||||
// ctx.boy = roles
|
||||
// })
|
||||
}
|
37
packages/roleypoly-server/api/ui.js
Normal file
37
packages/roleypoly-server/api/ui.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
// @flow
|
||||
import { type Context } from 'koa'
|
||||
import { type AppContext, type Router } from '../Roleypoly'
|
||||
export default (R: Router, $: AppContext) => {
|
||||
// note, this file only contains stuff for complicated routes.
|
||||
// next.js will handle anything beyond this.
|
||||
const processMappings = (mapping: { [path: string]: { path: string, noAutoFix?: boolean } }) => {
|
||||
for (let p in mapping) {
|
||||
R.get(p, (ctx: Context) => {
|
||||
ctx.status = 200
|
||||
return $.ui.render(ctx.req, ctx.res, mapping[p].path || mapping[p], { ...ctx.query, ...ctx.params })
|
||||
})
|
||||
|
||||
const { path } = mapping[p]
|
||||
if (!mapping[p].noAutoFix) {
|
||||
R.get(path, ctx => ctx.redirect(p))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processMappings({
|
||||
'/s/add': { path: '/_internal/_server_add' },
|
||||
'/s/:id': { path: '/_internal/_server', noAutoFix: true },
|
||||
'/test': { path: '/test_wwsw' }
|
||||
})
|
||||
|
||||
// edge cases
|
||||
R.get('/_internal/_server', (ctx: Context) => {
|
||||
if (ctx.query.id) {
|
||||
return ctx.redirect(`/s/${ctx.query.id}`)
|
||||
}
|
||||
|
||||
return ctx.redirect('/s/add')
|
||||
})
|
||||
|
||||
R.get('/s/', (ctx: Context) => ctx.redirect('/s/add'))
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue