UNPKG

immers

Version:

ActivityPub server for the metaverse

154 lines (150 loc) 4.93 kB
const { ObjectId } = require('mongodb') const uid = require('uid-safe') const bcrypt = require('bcrypt') const crypto = require('crypto') const { domain, hub, name } = process.env const saltRounds = 10 const tokenAge = 24 * 60 * 60 * 1000 // one day function hashEmail (email) { return crypto.createHash('sha256').update(email.toLowerCase()).digest('base64') } let db module.exports = { async setup (connection) { db = connection // token expiration via record deletion await db.collection('tokens').createIndex({ expiry: 1 }, { name: 'tokens-ttl', expireAfterSeconds: 0 }) await db.collection('clients').createIndex({ clientId: 1 }, { unique: true }) await db.collection('remotes').createIndex({ domain: 1 }, { unique: true }) await db.collection('users').createIndex({ username: 1 }, { unique: true }) await db.collection('users').createIndex({ email: 1 }, { unique: true }) // trusted client entry for local hub await db.collection('clients').findOneAndReplace({ clientId: `https://${domain}/o/immer` }, { name, clientId: `https://${domain}/o/immer`, redirectUri: `https://${hub}`, isTrusted: true }, { upsert: true }) }, // passport / oauth2orize methods async validateUser (username, password, done) { try { username = username.toLowerCase() const user = await db.collection('users').findOne({ username }) if (!user || !user.passwordHash) { return done(null, false) } const match = await bcrypt.compare(password, user.passwordHash) done(null, match && user) } catch (err) { done(err) } }, serializeClient (client, done) { done(null, client._id) }, async deserializeClient (id, done) { try { return done(null, await db.collection('clients').findOne({ _id: ObjectId(id) })) } catch (err) { done(err) } }, async validateClient (clientId, redirectUriFull, done) { try { // allow hub room id to be appended to registered Uri const url = new URL(redirectUriFull) const redirectUri = `${url.protocol}//${url.host}` const client = await db.collection('clients').findOne({ clientId, redirectUri }) if (!client) { return done(null, false) } return done(null, client, redirectUriFull) } catch (err) { done(err) } }, serializeUser (user, done) { done(null, user._id) }, async deserializeUser (id, done) { try { done(null, await db.collection('users').findOne({ _id: ObjectId(id) })) } catch (err) { done(err) } }, async createAccessToken (client, user, ares, done) { try { const tokenType = 'Bearer' const clientId = client.clientId const token = await uid(128) const expiry = new Date(Date.now() + tokenAge) await db.collection('tokens') .insertOne({ token, user, clientId, expiry, tokenType, origin: ares.origin, scope: ares.scope }) return done(null, token, { token_type: tokenType, issuer: ares.issuer, scope: ares.scope.join(' ') }) } catch (err) { done(err) } }, async validateAccessToken (token, done) { try { const tokenDoc = await db.collection('tokens').findOne({ token }) if (!tokenDoc) { return done(null, false) } done(null, tokenDoc.user, { scope: tokenDoc.scope, origin: tokenDoc.origin }) } catch (err) { done(err) } }, // immers api methods (promises instead of callbacks) getUserByName (username) { username = username.toLowerCase() return db.collection('users').findOne({ username }) }, getUserByEmail (email) { email = hashEmail(email) return db.collection('users').findOne({ email }) }, async createUser (username, password, email) { const user = { username } user.passwordHash = await bcrypt.hash(password, saltRounds) user.email = hashEmail(email) await db.collection('users').insertOne(user) return user }, async setPassword (username, password) { const passwordHash = await bcrypt.hash(password, saltRounds) const result = await db.collection('users') .updateOne({ username }, { $set: { passwordHash } }) return result.modifiedCount }, async createClient (clientId, redirectUri, name) { const client = { clientId, name } client.redirectUri = Array.isArray(redirectUri) ? redirectUri : [redirectUri] await db.collection('clients') .insertOne(client, { forceServerObjectId: true }) return client }, getRemoteClient (domain) { return db.collection('remotes').findOne({ domain }) }, async saveRemoteClient (domain, client) { const result = await db.collection('remotes').insertOne({ domain, clientId: client.clientId, redirectUri: client.redirectUri }) if (!result.insertedCount) { throw new Error('Error saving remove client') } } }