Whalebird-desktop-client-ma.../src/main/auth.ts

120 lines
3.5 KiB
TypeScript

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.social'
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<string> {
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<string> {
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 {}