import generator, { ProxyConfig } from 'megalodon' import crypto from 'crypto' import Account from './account' import { LocalAccount } from '~/src/types/localAccount' const appName = 'Whalebird' const appURL = 'https://whalebird.org' export default class Authentication { private db: Account private baseURL: string | null = null private domain: string | null = null private clientId: string | null = null private clientSecret: string | null = null private sessionToken: string | null = null private protocol: 'http' | 'https' constructor(accountDB: Account) { this.db = accountDB this.protocol = 'https' } setOtherInstance(domain: string) { this.baseURL = `${this.protocol}://${domain}` this.domain = domain this.clientId = null this.clientSecret = null } async getAuthorizationUrl( sns: 'mastodon' | 'pleroma' | 'misskey', domain: string = 'mastodon.social', proxy: ProxyConfig | false ): Promise { this.setOtherInstance(domain) if (!this.baseURL || !this.domain) { throw new Error('domain is required') } const client = generator(sns, this.baseURL, null, 'Whalebird', proxy) const res = await client.registerApp(appName, { website: appURL }) this.clientId = res.clientId this.clientSecret = res.clientSecret this.sessionToken = res.session_token const order = await this.db .lastAccount() .then(account => account.order + 1) .catch(err => { console.log(err) return 1 }) const local: LocalAccount = { baseURL: this.baseURL, domain: this.domain, clientId: this.clientId, clientSecret: this.clientSecret, accessToken: null, refreshToken: null, username: null, accountId: null, avatar: null, order: order } await this.db.insertAccount(local) if (res.url === null) { throw new AuthenticationURLError('Can not get url') } return res.url } async getAndUpdateAccessToken(sns: 'mastodon' | 'pleroma' | 'misskey', code: string | null, proxy: ProxyConfig | false): Promise { if (!this.baseURL) { throw new Error('domain is required') } if (!this.clientSecret) { throw new Error('client secret is required') } const client = generator(sns, this.baseURL, null, 'Whalebird', proxy) // In Misskey session token is required instead of authentication code. let authCode = code if (!code) { authCode = this.sessionToken } if (!authCode) { throw new Error('auth code is required') } const tokenData = await client.fetchAccessToken(this.clientId, this.clientSecret, authCode, 'urn:ietf:wg:oauth:2.0:oob') const search = { baseURL: this.baseURL, domain: this.domain, clientId: this.clientId, clientSecret: this.clientSecret } const rec = await this.db.searchAccount(search) let accessToken = tokenData.accessToken // In misskey, access token is sha256(userToken + clientSecret) if (sns === 'misskey') { accessToken = crypto .createHash('sha256') .update(tokenData.accessToken + this.clientSecret, 'utf8') .digest('hex') } const refreshToken = tokenData.refreshToken const data = await this.db.fetchAccount(sns, rec, accessToken, proxy) await this.db.updateAccount(rec._id!, { username: data.username, accountId: data.id, avatar: data.avatar, accessToken: accessToken, refreshToken: refreshToken }) return accessToken } } class AuthenticationURLError extends Error {}