@dotenvx/dotenvx-vlt 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +652 -0
- package/LICENSE +71 -0
- package/README.md +11 -0
- package/package.json +77 -0
- package/src/cli/actions/armor/down.js +37 -0
- package/src/cli/actions/armor/move.js +42 -0
- package/src/cli/actions/armor/pull.js +37 -0
- package/src/cli/actions/armor/push.js +37 -0
- package/src/cli/actions/armor/rotate.js +25 -0
- package/src/cli/actions/armor/up.js +37 -0
- package/src/cli/actions/backup.js +93 -0
- package/src/cli/actions/gateway/start.js +17 -0
- package/src/cli/actions/get.js +34 -0
- package/src/cli/actions/keypair.js +59 -0
- package/src/cli/actions/login.js +110 -0
- package/src/cli/actions/logout.js +36 -0
- package/src/cli/actions/observe.js +36 -0
- package/src/cli/actions/open.js +37 -0
- package/src/cli/actions/rotate/github/connect.js +84 -0
- package/src/cli/actions/rotate/npm/connect.js +84 -0
- package/src/cli/actions/rotate/openai/connect.js +84 -0
- package/src/cli/actions/rotate.js +44 -0
- package/src/cli/actions/set.js +34 -0
- package/src/cli/actions/settings/device.js +24 -0
- package/src/cli/actions/settings/hostname.js +20 -0
- package/src/cli/actions/settings/off.js +17 -0
- package/src/cli/actions/settings/on.js +17 -0
- package/src/cli/actions/settings/path.js +21 -0
- package/src/cli/actions/settings/token.js +24 -0
- package/src/cli/actions/settings/username.js +22 -0
- package/src/cli/actions/status.js +13 -0
- package/src/cli/actions/sync.js +104 -0
- package/src/cli/commands/armor.js +73 -0
- package/src/cli/commands/gateway.js +16 -0
- package/src/cli/commands/rotate/github.js +26 -0
- package/src/cli/commands/rotate/npm.js +26 -0
- package/src/cli/commands/rotate/openai.js +26 -0
- package/src/cli/commands/rotate.js +32 -0
- package/src/cli/commands/settings.js +60 -0
- package/src/cli/dotenvx-ops.js +163 -0
- package/src/cli/postinstall.js +16 -0
- package/src/db/device.js +73 -0
- package/src/db/session.js +193 -0
- package/src/lib/api/getAccount.js +32 -0
- package/src/lib/api/getSynchronization.js +34 -0
- package/src/lib/api/getVersion.js +24 -0
- package/src/lib/api/postArmorDown.js +48 -0
- package/src/lib/api/postArmorMove.js +48 -0
- package/src/lib/api/postArmorPull.js +48 -0
- package/src/lib/api/postArmorPush.js +48 -0
- package/src/lib/api/postArmorUp.js +51 -0
- package/src/lib/api/postBackup.js +68 -0
- package/src/lib/api/postGet.js +37 -0
- package/src/lib/api/postKeypair.js +60 -0
- package/src/lib/api/postLogout.js +34 -0
- package/src/lib/api/postOauthDeviceCode.js +45 -0
- package/src/lib/api/postOauthToken.js +38 -0
- package/src/lib/api/postObserve.js +62 -0
- package/src/lib/api/postRotate.js +39 -0
- package/src/lib/api/postRotateConnect.js +54 -0
- package/src/lib/api/postSet.js +43 -0
- package/src/lib/api/postSync.js +68 -0
- package/src/lib/helpers/armoredKeyDisplay.js +10 -0
- package/src/lib/helpers/buildApiError.js +16 -0
- package/src/lib/helpers/buildOauthError.js +13 -0
- package/src/lib/helpers/canonicalEnvFilename.js +13 -0
- package/src/lib/helpers/clipboardy/fallbacks/linux/xsel +0 -0
- package/src/lib/helpers/clipboardy/fallbacks/windows/clipboard_i686.exe +0 -0
- package/src/lib/helpers/clipboardy/fallbacks/windows/clipboard_x86_64.exe +0 -0
- package/src/lib/helpers/clipboardy/linux.js +57 -0
- package/src/lib/helpers/clipboardy/macos.js +14 -0
- package/src/lib/helpers/clipboardy/termux.js +41 -0
- package/src/lib/helpers/clipboardy/windows.js +16 -0
- package/src/lib/helpers/clipboardy.js +51 -0
- package/src/lib/helpers/confirm.js +17 -0
- package/src/lib/helpers/createSpinner.js +101 -0
- package/src/lib/helpers/createSpinner2.js +24 -0
- package/src/lib/helpers/decryptValue.js +10 -0
- package/src/lib/helpers/dotenvxProjectId.js +36 -0
- package/src/lib/helpers/encryptValue.js +9 -0
- package/src/lib/helpers/errors.js +30 -0
- package/src/lib/helpers/formatCode.js +11 -0
- package/src/lib/helpers/gitBranch.js +13 -0
- package/src/lib/helpers/gitUrl.js +13 -0
- package/src/lib/helpers/http.js +17 -0
- package/src/lib/helpers/jsonToEnv.js +7 -0
- package/src/lib/helpers/keyNamesForEnvFile.js +43 -0
- package/src/lib/helpers/keypairMetadata.js +88 -0
- package/src/lib/helpers/likelyUpdateCommand.js +33 -0
- package/src/lib/helpers/localKeypair.js +12 -0
- package/src/lib/helpers/mask.js +12 -0
- package/src/lib/helpers/normalizePath.js +5 -0
- package/src/lib/helpers/normalizeToken.js +5 -0
- package/src/lib/helpers/openUrl.js +7 -0
- package/src/lib/helpers/packageJson.js +3 -0
- package/src/lib/helpers/playwrightConnect.js +29 -0
- package/src/lib/helpers/pluralize.js +10 -0
- package/src/lib/helpers/prompts.js +68 -0
- package/src/lib/helpers/removeEnvKey.js +50 -0
- package/src/lib/helpers/safeRealpath.js +13 -0
- package/src/lib/helpers/sha256File.js +9 -0
- package/src/lib/helpers/smartMask.js +11 -0
- package/src/lib/helpers/truncate.js +10 -0
- package/src/lib/helpers/upsertEnvKey.js +61 -0
- package/src/lib/main.d.ts +152 -0
- package/src/lib/main.js +114 -0
- package/src/lib/services/armorDown.js +77 -0
- package/src/lib/services/armorMove.js +54 -0
- package/src/lib/services/armorPull.js +77 -0
- package/src/lib/services/armorPush.js +82 -0
- package/src/lib/services/armorUp.js +79 -0
- package/src/lib/services/backup.js +105 -0
- package/src/lib/services/gatewayStart.js +132 -0
- package/src/lib/services/keypair.js +59 -0
- package/src/lib/services/loggedIn.js +24 -0
- package/src/lib/services/login.js +28 -0
- package/src/lib/services/loginPoll.js +43 -0
- package/src/lib/services/logout.js +28 -0
- package/src/lib/services/rotate.js +28 -0
- package/src/lib/services/rotateGithubConnect.js +56 -0
- package/src/lib/services/rotateNpmConnect.js +56 -0
- package/src/lib/services/rotateOpenaiConnect.js +60 -0
- package/src/lib/services/status.js +11 -0
- package/src/lib/services/sync.js +70 -0
- package/src/lib/services/syncConflict.js +36 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const dotenvx = require('@dotenvx/dotenvx')
|
|
2
|
+
const prompts = require('../helpers/prompts')
|
|
3
|
+
const PostArmorUp = require('../api/postArmorUp')
|
|
4
|
+
const keyNamesForEnvFile = require('../helpers/keyNamesForEnvFile')
|
|
5
|
+
const removeEnvKey = require('../helpers/removeEnvKey')
|
|
6
|
+
|
|
7
|
+
function teamChoicesFromMeta (meta) {
|
|
8
|
+
return meta.organizations.map(org => ({
|
|
9
|
+
name: org.provider_slug,
|
|
10
|
+
value: org.provider_slug
|
|
11
|
+
}))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class ArmorUp {
|
|
15
|
+
constructor (hostname, token, devicePublicKey, envFile = '.env', team = undefined) {
|
|
16
|
+
this.hostname = hostname
|
|
17
|
+
this.token = token
|
|
18
|
+
this.devicePublicKey = devicePublicKey
|
|
19
|
+
this.envFile = envFile
|
|
20
|
+
this.team = team
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async run () {
|
|
24
|
+
const hostname = this.hostname
|
|
25
|
+
const token = this.token
|
|
26
|
+
const devicePublicKey = this.devicePublicKey
|
|
27
|
+
const envFile = this.envFile
|
|
28
|
+
const team = this.team
|
|
29
|
+
|
|
30
|
+
const {
|
|
31
|
+
publicKeyName,
|
|
32
|
+
privateKeyName
|
|
33
|
+
} = keyNamesForEnvFile(envFile)
|
|
34
|
+
|
|
35
|
+
const publicKey = dotenvx.get(publicKeyName, { path: envFile, strict: true, ignore: ['MISSING_PRIVATE_KEY'], noOps: true })
|
|
36
|
+
const privateKey = dotenvx.get(privateKeyName, { path: '.env.keys', strict: true, ignore: ['MISSING_KEY'], noOps: true })
|
|
37
|
+
|
|
38
|
+
let json
|
|
39
|
+
|
|
40
|
+
if (team) {
|
|
41
|
+
json = await new PostArmorUp(hostname, token, devicePublicKey, publicKey, privateKey, team).run()
|
|
42
|
+
} else {
|
|
43
|
+
try {
|
|
44
|
+
json = await new PostArmorUp(hostname, token, devicePublicKey, publicKey, privateKey, undefined).run()
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error.code !== 'DOTENVX_TEAM_REQUIRED') {
|
|
47
|
+
throw error
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const choices = teamChoicesFromMeta(error.meta)
|
|
51
|
+
|
|
52
|
+
let team = choices[0].value
|
|
53
|
+
if (choices.length > 1) {
|
|
54
|
+
team = await prompts.select({
|
|
55
|
+
message: 'Select team',
|
|
56
|
+
choices
|
|
57
|
+
}, {
|
|
58
|
+
input: process.stdin,
|
|
59
|
+
output: process.stderr
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
json = await new PostArmorUp(hostname, token, devicePublicKey, publicKey, privateKey, team).run()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
removeEnvKey(privateKeyName)
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
...json,
|
|
71
|
+
changed: json.changed,
|
|
72
|
+
privateKeyName,
|
|
73
|
+
privateKeyValue: json.private_key,
|
|
74
|
+
publicKeyValue: publicKey
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = ArmorUp
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const si = require('systeminformation')
|
|
4
|
+
const dotenvx = require('@dotenvx/dotenvx')
|
|
5
|
+
const prompts = require('../helpers/prompts')
|
|
6
|
+
|
|
7
|
+
const Session = require('./../../db/session')
|
|
8
|
+
|
|
9
|
+
const gitUrl = require('./../helpers/gitUrl')
|
|
10
|
+
const gitBranch = require('./../helpers/gitBranch')
|
|
11
|
+
const dotenvxProjectId = require('./../helpers/dotenvxProjectId')
|
|
12
|
+
|
|
13
|
+
// api calls
|
|
14
|
+
const GetAccount = require('./../api/getAccount')
|
|
15
|
+
const PostBackup = require('./../api/postBackup')
|
|
16
|
+
|
|
17
|
+
class Backup {
|
|
18
|
+
constructor (hostname, org = null) {
|
|
19
|
+
this.hostname = hostname
|
|
20
|
+
this.org = org
|
|
21
|
+
this.cwd = process.cwd()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async run () {
|
|
25
|
+
const sesh = new Session()
|
|
26
|
+
const token = sesh.token()
|
|
27
|
+
const devicePublicKey = sesh.devicePublicKey()
|
|
28
|
+
let _org = this.org
|
|
29
|
+
let projectEnvXFileNeedsWrite = false
|
|
30
|
+
|
|
31
|
+
// required
|
|
32
|
+
const files = this._files()
|
|
33
|
+
const payload = { files }
|
|
34
|
+
const encoded = Buffer.from(JSON.stringify(payload)).toString('base64')
|
|
35
|
+
|
|
36
|
+
// user must be logged in to use feature
|
|
37
|
+
const accountJson = await new GetAccount(this.hostname, token).run()
|
|
38
|
+
|
|
39
|
+
// optional project id
|
|
40
|
+
const _dotenvxProjectId = dotenvxProjectId(this.cwd, false)
|
|
41
|
+
|
|
42
|
+
// missing .env.x file
|
|
43
|
+
if (!_dotenvxProjectId) {
|
|
44
|
+
projectEnvXFileNeedsWrite = true // for writing
|
|
45
|
+
|
|
46
|
+
// set org
|
|
47
|
+
if (!_org) {
|
|
48
|
+
const choices = accountJson.organizations.map(o => ({
|
|
49
|
+
name: o.provider_slug,
|
|
50
|
+
value: o.provider_slug
|
|
51
|
+
}))
|
|
52
|
+
|
|
53
|
+
if (choices.length === 1) {
|
|
54
|
+
_org = choices[0].value // just use first choice
|
|
55
|
+
} else {
|
|
56
|
+
_org = await prompts.select({
|
|
57
|
+
message: 'Select org',
|
|
58
|
+
choices
|
|
59
|
+
}, {
|
|
60
|
+
input: process.stdin,
|
|
61
|
+
output: process.stderr
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// optional
|
|
68
|
+
const _pwd = this.cwd
|
|
69
|
+
const _gitUrl = gitUrl()
|
|
70
|
+
const _gitBranch = gitBranch()
|
|
71
|
+
|
|
72
|
+
const system = await si.system()
|
|
73
|
+
const _systemUuid = system.uuid
|
|
74
|
+
|
|
75
|
+
const osInfo = await si.osInfo()
|
|
76
|
+
const _osPlatform = osInfo.platform
|
|
77
|
+
const _osArch = osInfo.arch
|
|
78
|
+
|
|
79
|
+
const data = await new PostBackup(this.hostname, token, devicePublicKey, encoded, _dotenvxProjectId, _org, _pwd, _gitUrl, _gitBranch, _systemUuid, _osPlatform, _osArch).run()
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
id: data.id,
|
|
83
|
+
dotenvxProjectId: data.dotenvx_project_id,
|
|
84
|
+
projectUsernameName: data.project_username_name,
|
|
85
|
+
projectEnvXSrc: data.project_env_x_src,
|
|
86
|
+
projectEnvXFileNeedsWrite,
|
|
87
|
+
files: data.files
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
_files () {
|
|
92
|
+
const out = []
|
|
93
|
+
const filepaths = dotenvx.ls(this.cwd, '.env.keys*')
|
|
94
|
+
|
|
95
|
+
for (const fp of filepaths) {
|
|
96
|
+
const abs = path.join(this.cwd, fp)
|
|
97
|
+
const src = fs.readFileSync(abs, 'utf8')
|
|
98
|
+
out.push({ filepath: fp, src })
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return out
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = Backup
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
const dotenvx = require('@dotenvx/dotenvx')
|
|
2
|
+
const { logger } = dotenvx
|
|
3
|
+
const express = require('express')
|
|
4
|
+
const { randomUUID } = require('node:crypto')
|
|
5
|
+
const { Readable } = require('node:stream')
|
|
6
|
+
|
|
7
|
+
class GatewayStart {
|
|
8
|
+
constructor (options = {}) {
|
|
9
|
+
this.port = Number(options.port) || 7278
|
|
10
|
+
this.hostname = options.hostname || '127.0.0.1'
|
|
11
|
+
this.upstreamBaseUrl = options.upstreamBaseUrl || 'https://api.openai.com/v1'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async run () {
|
|
15
|
+
dotenvx.config()
|
|
16
|
+
|
|
17
|
+
const app = express()
|
|
18
|
+
app.use(express.json({ limit: '10mb' }))
|
|
19
|
+
app.use((req, res, next) => {
|
|
20
|
+
const startedAt = process.hrtime.bigint()
|
|
21
|
+
const requestId = req.headers['x-request-id'] || randomUUID()
|
|
22
|
+
const fwd = req.headers['x-forwarded-for'] || req.socket.remoteAddress || '-'
|
|
23
|
+
const host = req.headers.host || `${this.hostname}:${this.port}`
|
|
24
|
+
const path = req.originalUrl || req.url
|
|
25
|
+
|
|
26
|
+
res.setHeader('x-request-id', requestId)
|
|
27
|
+
|
|
28
|
+
res.on('finish', () => {
|
|
29
|
+
const service = Math.max(1, Math.round(Number(process.hrtime.bigint() - startedAt) / 1e6))
|
|
30
|
+
const bytes = res.getHeader('content-length') || '-'
|
|
31
|
+
const protocol = `http/${req.httpVersion}`
|
|
32
|
+
|
|
33
|
+
console.log(
|
|
34
|
+
`at=info method=${req.method} path="${path}" host=${host} request_id=${requestId} ` +
|
|
35
|
+
`fwd="${fwd}" dyno=web.1 connect=0ms service=${service}ms status=${res.statusCode} bytes=${bytes} protocol=${protocol}`
|
|
36
|
+
)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
next()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
app.get('/', (req, res) => {
|
|
43
|
+
res.json({ service: 'dotenvx gateway', ok: true })
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
// Optional: health
|
|
47
|
+
app.get('/healthz', (req, res) => res.status(200).json({ ok: true }))
|
|
48
|
+
|
|
49
|
+
// OpenAI Responses API (required for pi openai defaults)
|
|
50
|
+
app.post('/v1/responses', (req, res) => this.handleProxy(req, res, '/responses'))
|
|
51
|
+
|
|
52
|
+
// Optional: OpenAI Chat Completions compatibility
|
|
53
|
+
app.post('/v1/chat/completions', (req, res) => this.handleProxy(req, res, '/chat/completions'))
|
|
54
|
+
|
|
55
|
+
// Optional: models passthrough
|
|
56
|
+
app.get('/v1/models', (req, res) => this.handleProxy(req, res, '/models'))
|
|
57
|
+
|
|
58
|
+
return await new Promise((resolve, reject) => {
|
|
59
|
+
const server = app.listen(this.port, this.hostname, () => {
|
|
60
|
+
logger.successv(`dotenvx gateway listening on http://${this.hostname}:${this.port}`)
|
|
61
|
+
resolve(server)
|
|
62
|
+
})
|
|
63
|
+
server.on('error', reject)
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async handleProxy (req, res, upstreamPath) {
|
|
68
|
+
try {
|
|
69
|
+
// 1) Authenticate caller token (gateway token / vestauth token)
|
|
70
|
+
const auth = req.headers.authorization || ''
|
|
71
|
+
const token = auth.startsWith('Bearer ') ? auth.slice(7) : null
|
|
72
|
+
if (!token) return res.status(401).json({ error: { message: 'Missing bearer token' } })
|
|
73
|
+
|
|
74
|
+
// TODO: replace with real vestauth verification
|
|
75
|
+
// const subject = await this.verifyGatewayToken(token)
|
|
76
|
+
// if (!subject) return res.status(403).json({ error: { message: 'Invalid token' } })
|
|
77
|
+
|
|
78
|
+
// 2) Fetch provider secret just-in-time (dotenvx/as2)
|
|
79
|
+
const openaiApiKey = await this.getOpenAIKeyForSubject()
|
|
80
|
+
if (!openaiApiKey) return res.status(500).json({ error: { message: 'No upstream key available' } })
|
|
81
|
+
|
|
82
|
+
// 3) Forward request upstream
|
|
83
|
+
const upstreamUrl = `${this.upstreamBaseUrl}${upstreamPath}`
|
|
84
|
+
|
|
85
|
+
const upstreamResp = await fetch(upstreamUrl, {
|
|
86
|
+
method: req.method,
|
|
87
|
+
headers: {
|
|
88
|
+
'content-type': 'application/json',
|
|
89
|
+
authorization: `Bearer ${openaiApiKey}`
|
|
90
|
+
},
|
|
91
|
+
body: req.method === 'GET' ? undefined : JSON.stringify(req.body)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// 4) Pass through status + relevant headers
|
|
95
|
+
res.status(upstreamResp.status)
|
|
96
|
+
const contentType = upstreamResp.headers.get('content-type') || 'application/json'
|
|
97
|
+
res.setHeader('content-type', contentType)
|
|
98
|
+
|
|
99
|
+
const requestId = upstreamResp.headers.get('x-request-id')
|
|
100
|
+
if (requestId) res.setHeader('x-request-id', requestId)
|
|
101
|
+
|
|
102
|
+
// 5) Stream SSE directly when needed
|
|
103
|
+
if (contentType.includes('text/event-stream') && upstreamResp.body) {
|
|
104
|
+
res.setHeader('cache-control', 'no-cache')
|
|
105
|
+
res.setHeader('connection', 'keep-alive')
|
|
106
|
+
Readable.fromWeb(upstreamResp.body).pipe(res)
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 6) Non-stream response
|
|
111
|
+
const text = await upstreamResp.text()
|
|
112
|
+
res.send(text)
|
|
113
|
+
} catch (err) {
|
|
114
|
+
logger.error(`gateway proxy error: ${err?.message || err}`)
|
|
115
|
+
res.status(500).json({ error: { message: 'Gateway proxy failed' } })
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async verifyGatewayToken (token) {
|
|
120
|
+
// TODO: verify with vestauth/dotenvx auth
|
|
121
|
+
// return subject string (e.g. user/org id) if valid
|
|
122
|
+
return token ? 'vestauth:user:123' : null
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async getOpenAIKeyForSubject (subject = null) {
|
|
126
|
+
// TODO: call dotenvx.com/as2 with subject context and fetch secret JIT
|
|
127
|
+
// IMPORTANT: never log this value
|
|
128
|
+
return process.env.OPENAI_API_KEY // placeholder for initial test only
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = GatewayStart
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const prompts = require('../helpers/prompts')
|
|
2
|
+
const PostKeypair = require('../api/postKeypair')
|
|
3
|
+
const keypairMetadata = require('../helpers/keypairMetadata')
|
|
4
|
+
|
|
5
|
+
function teamChoicesFromMeta (meta) {
|
|
6
|
+
return meta.organizations.map(org => ({
|
|
7
|
+
name: org.provider_slug,
|
|
8
|
+
value: org.provider_slug
|
|
9
|
+
}))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
class Keypair {
|
|
13
|
+
constructor (hostname, token, devicePublicKey, publicKey, team = undefined, envFile = '.env') {
|
|
14
|
+
this.hostname = hostname
|
|
15
|
+
this.token = token
|
|
16
|
+
this.devicePublicKey = devicePublicKey
|
|
17
|
+
this.publicKey = publicKey
|
|
18
|
+
this.team = team
|
|
19
|
+
this.envFile = envFile
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async run () {
|
|
23
|
+
const hostname = this.hostname
|
|
24
|
+
const token = this.token
|
|
25
|
+
const devicePublicKey = this.devicePublicKey
|
|
26
|
+
const publicKey = this.publicKey
|
|
27
|
+
const team = this.team
|
|
28
|
+
const metadata = keypairMetadata(this.envFile)
|
|
29
|
+
|
|
30
|
+
if (team) {
|
|
31
|
+
return await new PostKeypair(hostname, token, devicePublicKey, publicKey, team, metadata).run()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
return await new PostKeypair(hostname, token, devicePublicKey, publicKey, undefined, metadata).run()
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (error.code !== 'DOTENVX_TEAM_REQUIRED') {
|
|
38
|
+
throw error
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const choices = teamChoicesFromMeta(error.meta)
|
|
42
|
+
|
|
43
|
+
let team = choices[0].value
|
|
44
|
+
if (choices.length > 1) {
|
|
45
|
+
team = await prompts.select({
|
|
46
|
+
message: 'Select team',
|
|
47
|
+
choices
|
|
48
|
+
}, {
|
|
49
|
+
input: process.stdin,
|
|
50
|
+
output: process.stderr
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return await new PostKeypair(hostname, token, devicePublicKey, publicKey, team, metadata).run()
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = Keypair
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const Session = require('./../../db/session')
|
|
2
|
+
const GetAccount = require('./../../lib/api/getAccount')
|
|
3
|
+
|
|
4
|
+
class LoggedIn {
|
|
5
|
+
constructor (hostname) {
|
|
6
|
+
this.hostname = hostname
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async run () {
|
|
10
|
+
const hostname = this.hostname
|
|
11
|
+
const sesh = new Session()
|
|
12
|
+
const token = sesh.token()
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
await new GetAccount(hostname, token).run()
|
|
16
|
+
return true
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error.code === 'unauthorized') return false
|
|
19
|
+
throw error
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = LoggedIn
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const Session = require('./../../db/session')
|
|
2
|
+
|
|
3
|
+
// api calls
|
|
4
|
+
const PostOauthDeviceCode = require('./../../lib/api/postOauthDeviceCode')
|
|
5
|
+
|
|
6
|
+
class Login {
|
|
7
|
+
constructor (hostname) {
|
|
8
|
+
this.hostname = hostname
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async run () {
|
|
12
|
+
const sesh = new Session()
|
|
13
|
+
const devicePublicKey = sesh.devicePublicKey()
|
|
14
|
+
const systemInformation = await sesh.systemInformation()
|
|
15
|
+
|
|
16
|
+
const data = await new PostOauthDeviceCode(this.hostname, devicePublicKey, systemInformation).run()
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
deviceCode: data.device_code,
|
|
20
|
+
userCode: data.user_code,
|
|
21
|
+
verificationUri: data.verification_uri,
|
|
22
|
+
verificationUriComplete: data.verification_uri_complete,
|
|
23
|
+
interval: data.interval
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = Login
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const Session = require('./../../db/session')
|
|
2
|
+
|
|
3
|
+
// api calls
|
|
4
|
+
const PostOauthToken = require('./../../lib/api/postOauthToken')
|
|
5
|
+
|
|
6
|
+
class LoginPoll {
|
|
7
|
+
constructor (hostname, deviceCode, interval) {
|
|
8
|
+
this.hostname = hostname
|
|
9
|
+
this.deviceCode = deviceCode
|
|
10
|
+
this.interval = interval
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async run () {
|
|
14
|
+
const hostname = this.hostname
|
|
15
|
+
const deviceCode = this.deviceCode
|
|
16
|
+
const interval = this.interval
|
|
17
|
+
|
|
18
|
+
const sesh = new Session()
|
|
19
|
+
|
|
20
|
+
while (true) {
|
|
21
|
+
try {
|
|
22
|
+
const data = await new PostOauthToken(hostname, deviceCode).run()
|
|
23
|
+
|
|
24
|
+
if (data.access_token) {
|
|
25
|
+
sesh.login(hostname, data.id, data.username, data.access_token) // log in user
|
|
26
|
+
return data
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
await new Promise(resolve => setTimeout(resolve, interval * 1000))
|
|
30
|
+
} catch (error) {
|
|
31
|
+
// continue polling if authorization_pending
|
|
32
|
+
if (error.code === 'authorization_pending') {
|
|
33
|
+
const newInterval = interval + 1 // grow the interval
|
|
34
|
+
await new Promise(resolve => setTimeout(resolve, newInterval * 1000))
|
|
35
|
+
} else {
|
|
36
|
+
throw error
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = LoginPoll
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const Session = require('./../../db/session')
|
|
2
|
+
|
|
3
|
+
// api calls
|
|
4
|
+
const PostLogout = require('./../../lib/api/postLogout')
|
|
5
|
+
|
|
6
|
+
class Logout {
|
|
7
|
+
constructor (hostname) {
|
|
8
|
+
this.hostname = hostname
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async run () {
|
|
12
|
+
const sesh = new Session()
|
|
13
|
+
const token = sesh.token()
|
|
14
|
+
|
|
15
|
+
const data = await new PostLogout(this.hostname, token).run()
|
|
16
|
+
|
|
17
|
+
const id = data.id
|
|
18
|
+
const username = data.username
|
|
19
|
+
const accessToken = data.access_token
|
|
20
|
+
const settingsDevicesUrl = `${this.hostname}/settings/devices`
|
|
21
|
+
|
|
22
|
+
sesh.logout(this.hostname, id, accessToken)
|
|
23
|
+
|
|
24
|
+
return { username, accessToken, settingsDevicesUrl }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = Logout
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const PostRotate = require('./../api/postRotate')
|
|
2
|
+
|
|
3
|
+
class Rotate {
|
|
4
|
+
constructor (hostname, token, inputUri) {
|
|
5
|
+
this.hostname = hostname
|
|
6
|
+
this.token = token
|
|
7
|
+
this.inputUri = inputUri
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async run () {
|
|
11
|
+
const hostname = this.hostname
|
|
12
|
+
const token = this.token
|
|
13
|
+
const inputUri = this.inputUri
|
|
14
|
+
|
|
15
|
+
const json = await new PostRotate(hostname, token, inputUri).run()
|
|
16
|
+
const uri = json.uri
|
|
17
|
+
const url = json.url
|
|
18
|
+
const rotUid = json.rot_uid
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
uri,
|
|
22
|
+
url,
|
|
23
|
+
rotUid
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = Rotate
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const PostRotateConnect = require('./../api/postRotateConnect')
|
|
2
|
+
|
|
3
|
+
const playwrightConnect = require('./../helpers/playwrightConnect')
|
|
4
|
+
|
|
5
|
+
const TIMEOUT = 500 // visibility timeout
|
|
6
|
+
const SLUG = 'github' // hardcoded
|
|
7
|
+
|
|
8
|
+
class RotateGithubConnect {
|
|
9
|
+
constructor (hostname, token, org, username, password, email = null) {
|
|
10
|
+
this.hostname = hostname
|
|
11
|
+
this.org = org
|
|
12
|
+
this.token = token
|
|
13
|
+
this.username = username
|
|
14
|
+
this.password = password
|
|
15
|
+
|
|
16
|
+
// optional
|
|
17
|
+
this.email = email
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async run () {
|
|
21
|
+
const hostname = this.hostname
|
|
22
|
+
const token = this.token
|
|
23
|
+
const org = this.org
|
|
24
|
+
const username = this.username
|
|
25
|
+
const password = this.password
|
|
26
|
+
const email = this.email
|
|
27
|
+
|
|
28
|
+
const { browser, context, page } = await playwrightConnect()
|
|
29
|
+
|
|
30
|
+
const usernameSelector = 'input[type="text"]'
|
|
31
|
+
const passwordSelector = 'input[type="password"]'
|
|
32
|
+
|
|
33
|
+
await page.goto('https://github.com/login', { waitUntil: 'domcontentloaded' })
|
|
34
|
+
await page.waitForTimeout(TIMEOUT)
|
|
35
|
+
await page.fill(usernameSelector, username)
|
|
36
|
+
await page.waitForTimeout(TIMEOUT)
|
|
37
|
+
await page.fill(passwordSelector, password)
|
|
38
|
+
await page.waitForTimeout(TIMEOUT)
|
|
39
|
+
await page.press('input[type="password"]', 'Enter')
|
|
40
|
+
await page.waitForNavigation({ timeout: 0 })
|
|
41
|
+
await page.waitForSelector('img.avatar', { state: 'visible', timeout: 0 })
|
|
42
|
+
|
|
43
|
+
const playwrightStorageStateStringified = JSON.stringify(await context.storageState())
|
|
44
|
+
|
|
45
|
+
const { uid, url } = await new PostRotateConnect(hostname, token, org, SLUG, username, password, email, playwrightStorageStateStringified).run()
|
|
46
|
+
|
|
47
|
+
await browser.close()
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
uid,
|
|
51
|
+
url
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = RotateGithubConnect
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const PostRotateConnect = require('./../api/postRotateConnect')
|
|
2
|
+
|
|
3
|
+
const playwrightConnect = require('./../helpers/playwrightConnect')
|
|
4
|
+
|
|
5
|
+
const TIMEOUT = 500 // visibility timeout
|
|
6
|
+
const SLUG = 'npm'
|
|
7
|
+
|
|
8
|
+
class RotateNpmConnect {
|
|
9
|
+
constructor (hostname, token, org, username, password, email = null) {
|
|
10
|
+
this.hostname = hostname
|
|
11
|
+
this.org = org
|
|
12
|
+
this.token = token
|
|
13
|
+
this.username = username
|
|
14
|
+
this.password = password
|
|
15
|
+
|
|
16
|
+
// optional
|
|
17
|
+
this.email = email
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async run () {
|
|
21
|
+
const hostname = this.hostname
|
|
22
|
+
const token = this.token
|
|
23
|
+
const org = this.org
|
|
24
|
+
const username = this.username
|
|
25
|
+
const password = this.password
|
|
26
|
+
const email = this.email
|
|
27
|
+
|
|
28
|
+
const { browser, context, page } = await playwrightConnect()
|
|
29
|
+
|
|
30
|
+
const usernameSelector = 'input[type="text"]'
|
|
31
|
+
const passwordSelector = 'input[type="password"]'
|
|
32
|
+
|
|
33
|
+
await page.goto('https://npmjs.com/login', { waitUntil: 'domcontentloaded' })
|
|
34
|
+
await page.waitForTimeout(TIMEOUT)
|
|
35
|
+
await page.fill(usernameSelector, username)
|
|
36
|
+
await page.waitForTimeout(TIMEOUT)
|
|
37
|
+
await page.fill(passwordSelector, password)
|
|
38
|
+
await page.waitForTimeout(TIMEOUT)
|
|
39
|
+
await page.press('input[type="password"]', 'Enter')
|
|
40
|
+
await page.waitForNavigation({ timeout: 0 })
|
|
41
|
+
await page.waitForSelector('img[alt="avatar"]', { state: 'visible', timeout: 0 })
|
|
42
|
+
|
|
43
|
+
const playwrightStorageStateStringified = JSON.stringify(await context.storageState())
|
|
44
|
+
|
|
45
|
+
const { uid, url } = await new PostRotateConnect(hostname, token, org, SLUG, username, password, email, playwrightStorageStateStringified).run()
|
|
46
|
+
|
|
47
|
+
await browser.close()
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
uid,
|
|
51
|
+
url
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = RotateNpmConnect
|