@web3-storage/w3cli
Version:
💾 w3 command line interface
163 lines (143 loc) • 5.03 kB
JavaScript
import open from 'open'
import { confirm } from '@inquirer/prompts'
import * as Account from '@web3-storage/w3up-client/account'
import * as Result from '@web3-storage/w3up-client/result'
import * as DidMailto from '@web3-storage/did-mailto'
import { authorize } from '@web3-storage/capabilities/access'
import { base64url } from 'multiformats/bases/base64'
import { select } from '@inquirer/prompts'
import { getClient } from './lib.js'
import ora from 'ora'
/**
* @typedef {Awaited<ReturnType<Account.login>>['ok']&{}} View
*/
export const OAuthProviderGitHub = 'github'
const OAuthProviders = /** @type {const} */ ([OAuthProviderGitHub])
/** @type {Record<import('@web3-storage/w3up-client/types').DID, string>} */
const GitHubOauthClientIDs = {
'did:web:web3.storage': 'Ov23li0xr95ocCkZiwaD',
'did:web:staging.web3.storage': 'Ov23liDKQB1ePrcGy5HI',
}
/** @param {import('@web3-storage/w3up-client/types').DID} serviceID */
const getGithubOAuthClientID = serviceID => {
const id = process.env.GITHUB_OAUTH_CLIENT_ID || GitHubOauthClientIDs[serviceID]
if (!id) throw new Error(`missing OAuth client ID for: ${serviceID}`)
return id
}
/**
* @param {DidMailto.EmailAddress} [email]
* @param {object} [options]
* @param {boolean} [options.github]
*/
export const login = async (email, options) => {
let method
if (email) {
method = 'email'
} else if (options?.github) {
method = 'github'
} else {
method = await select({
message: 'How do you want to login?',
choices: [
{ name: 'Via Email', value: 'email' },
{ name: 'Via GitHub', value: 'github' },
],
})
}
if (method === 'email' && email) {
await loginWithClient(email, await getClient())
} else if (method === 'github') {
await oauthLoginWithClient(OAuthProviderGitHub, await getClient())
} else {
console.error('Error: please provide email address or specify flag for alternate login method')
process.exit(1)
}
}
/**
* @param {DidMailto.EmailAddress} email
* @param {import('@web3-storage/w3up-client').Client} client
* @returns {Promise<View>}
*/
export const loginWithClient = async (email, client) => {
/** @type {import('ora').Ora|undefined} */
let spinner
const timeout = setTimeout(() => {
spinner = ora(
`🔗 please click the link sent to ${email} to authorize this agent`
).start()
}, 1000)
try {
const account = Result.try(await Account.login(client, email))
Result.try(await account.save())
if (spinner) spinner.stop()
console.log(`⁂ Agent was authorized by ${account.did()}`)
return account
} catch (err) {
if (spinner) spinner.stop()
console.error(err)
process.exit(1)
} finally {
clearTimeout(timeout)
}
}
/**
* @param {(typeof OAuthProviders)[number]} provider OAuth provider
* @param {import('@web3-storage/w3up-client').Client} client
*/
export const oauthLoginWithClient = async (provider, client) => {
if (provider != OAuthProviderGitHub) {
console.error(`Error: unknown OAuth provider: ${provider}`)
process.exit(1)
}
/** @type {import('ora').Ora|undefined} */
let spinner
try {
// create access/authorize request
const request = await authorize.delegate({
audience: client.agent.connection.id,
issuer: client.agent.issuer,
// agent that should be granted access
with: client.agent.did(),
// capabilities requested (account access)
nb: { att: [{ can: '*' }] }
})
const archive = await request.archive()
if (archive.error) {
throw new Error('archiving access authorize delegation', { cause: archive.error })
}
const clientID = getGithubOAuthClientID(client.agent.connection.id.did())
const state = base64url.encode(archive.ok)
const loginURL = `https://github.com/login/oauth/authorize?scope=read:user,user:email&client_id=${clientID}&state=${state}`
if (await confirm({ message: 'Open the GitHub login URL in your default browser?' })) {
spinner = ora('Waiting for GitHub authorization to be completed in browser...').start()
await open(loginURL)
} else {
spinner = ora(`Click the link to authenticate with GitHub: ${loginURL}`).start()
}
const expiration = Math.floor(Date.now() / 1000) + (60 * 15)
const account = Result.unwrap(await Account.externalLogin(client, { request: request.cid, expiration }))
Result.unwrap(await account.save())
if (spinner) spinner.stop()
console.log(`⁂ Agent was authorized by ${account.did()}`)
return account
} catch (err) {
if (spinner) spinner.stop()
console.error(err)
process.exit(1)
}
}
/**
*
*/
export const list = async () => {
const client = await getClient()
const accounts = Object.values(Account.list(client))
for (const account of accounts) {
console.log(account.did())
}
if (accounts.length === 0) {
console.log(
'⁂ Agent has not been authorized yet. Try `w3 login` to authorize this agent with your account.'
)
}
}