@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.
Files changed (125) hide show
  1. package/CHANGELOG.md +652 -0
  2. package/LICENSE +71 -0
  3. package/README.md +11 -0
  4. package/package.json +77 -0
  5. package/src/cli/actions/armor/down.js +37 -0
  6. package/src/cli/actions/armor/move.js +42 -0
  7. package/src/cli/actions/armor/pull.js +37 -0
  8. package/src/cli/actions/armor/push.js +37 -0
  9. package/src/cli/actions/armor/rotate.js +25 -0
  10. package/src/cli/actions/armor/up.js +37 -0
  11. package/src/cli/actions/backup.js +93 -0
  12. package/src/cli/actions/gateway/start.js +17 -0
  13. package/src/cli/actions/get.js +34 -0
  14. package/src/cli/actions/keypair.js +59 -0
  15. package/src/cli/actions/login.js +110 -0
  16. package/src/cli/actions/logout.js +36 -0
  17. package/src/cli/actions/observe.js +36 -0
  18. package/src/cli/actions/open.js +37 -0
  19. package/src/cli/actions/rotate/github/connect.js +84 -0
  20. package/src/cli/actions/rotate/npm/connect.js +84 -0
  21. package/src/cli/actions/rotate/openai/connect.js +84 -0
  22. package/src/cli/actions/rotate.js +44 -0
  23. package/src/cli/actions/set.js +34 -0
  24. package/src/cli/actions/settings/device.js +24 -0
  25. package/src/cli/actions/settings/hostname.js +20 -0
  26. package/src/cli/actions/settings/off.js +17 -0
  27. package/src/cli/actions/settings/on.js +17 -0
  28. package/src/cli/actions/settings/path.js +21 -0
  29. package/src/cli/actions/settings/token.js +24 -0
  30. package/src/cli/actions/settings/username.js +22 -0
  31. package/src/cli/actions/status.js +13 -0
  32. package/src/cli/actions/sync.js +104 -0
  33. package/src/cli/commands/armor.js +73 -0
  34. package/src/cli/commands/gateway.js +16 -0
  35. package/src/cli/commands/rotate/github.js +26 -0
  36. package/src/cli/commands/rotate/npm.js +26 -0
  37. package/src/cli/commands/rotate/openai.js +26 -0
  38. package/src/cli/commands/rotate.js +32 -0
  39. package/src/cli/commands/settings.js +60 -0
  40. package/src/cli/dotenvx-ops.js +163 -0
  41. package/src/cli/postinstall.js +16 -0
  42. package/src/db/device.js +73 -0
  43. package/src/db/session.js +193 -0
  44. package/src/lib/api/getAccount.js +32 -0
  45. package/src/lib/api/getSynchronization.js +34 -0
  46. package/src/lib/api/getVersion.js +24 -0
  47. package/src/lib/api/postArmorDown.js +48 -0
  48. package/src/lib/api/postArmorMove.js +48 -0
  49. package/src/lib/api/postArmorPull.js +48 -0
  50. package/src/lib/api/postArmorPush.js +48 -0
  51. package/src/lib/api/postArmorUp.js +51 -0
  52. package/src/lib/api/postBackup.js +68 -0
  53. package/src/lib/api/postGet.js +37 -0
  54. package/src/lib/api/postKeypair.js +60 -0
  55. package/src/lib/api/postLogout.js +34 -0
  56. package/src/lib/api/postOauthDeviceCode.js +45 -0
  57. package/src/lib/api/postOauthToken.js +38 -0
  58. package/src/lib/api/postObserve.js +62 -0
  59. package/src/lib/api/postRotate.js +39 -0
  60. package/src/lib/api/postRotateConnect.js +54 -0
  61. package/src/lib/api/postSet.js +43 -0
  62. package/src/lib/api/postSync.js +68 -0
  63. package/src/lib/helpers/armoredKeyDisplay.js +10 -0
  64. package/src/lib/helpers/buildApiError.js +16 -0
  65. package/src/lib/helpers/buildOauthError.js +13 -0
  66. package/src/lib/helpers/canonicalEnvFilename.js +13 -0
  67. package/src/lib/helpers/clipboardy/fallbacks/linux/xsel +0 -0
  68. package/src/lib/helpers/clipboardy/fallbacks/windows/clipboard_i686.exe +0 -0
  69. package/src/lib/helpers/clipboardy/fallbacks/windows/clipboard_x86_64.exe +0 -0
  70. package/src/lib/helpers/clipboardy/linux.js +57 -0
  71. package/src/lib/helpers/clipboardy/macos.js +14 -0
  72. package/src/lib/helpers/clipboardy/termux.js +41 -0
  73. package/src/lib/helpers/clipboardy/windows.js +16 -0
  74. package/src/lib/helpers/clipboardy.js +51 -0
  75. package/src/lib/helpers/confirm.js +17 -0
  76. package/src/lib/helpers/createSpinner.js +101 -0
  77. package/src/lib/helpers/createSpinner2.js +24 -0
  78. package/src/lib/helpers/decryptValue.js +10 -0
  79. package/src/lib/helpers/dotenvxProjectId.js +36 -0
  80. package/src/lib/helpers/encryptValue.js +9 -0
  81. package/src/lib/helpers/errors.js +30 -0
  82. package/src/lib/helpers/formatCode.js +11 -0
  83. package/src/lib/helpers/gitBranch.js +13 -0
  84. package/src/lib/helpers/gitUrl.js +13 -0
  85. package/src/lib/helpers/http.js +17 -0
  86. package/src/lib/helpers/jsonToEnv.js +7 -0
  87. package/src/lib/helpers/keyNamesForEnvFile.js +43 -0
  88. package/src/lib/helpers/keypairMetadata.js +88 -0
  89. package/src/lib/helpers/likelyUpdateCommand.js +33 -0
  90. package/src/lib/helpers/localKeypair.js +12 -0
  91. package/src/lib/helpers/mask.js +12 -0
  92. package/src/lib/helpers/normalizePath.js +5 -0
  93. package/src/lib/helpers/normalizeToken.js +5 -0
  94. package/src/lib/helpers/openUrl.js +7 -0
  95. package/src/lib/helpers/packageJson.js +3 -0
  96. package/src/lib/helpers/playwrightConnect.js +29 -0
  97. package/src/lib/helpers/pluralize.js +10 -0
  98. package/src/lib/helpers/prompts.js +68 -0
  99. package/src/lib/helpers/removeEnvKey.js +50 -0
  100. package/src/lib/helpers/safeRealpath.js +13 -0
  101. package/src/lib/helpers/sha256File.js +9 -0
  102. package/src/lib/helpers/smartMask.js +11 -0
  103. package/src/lib/helpers/truncate.js +10 -0
  104. package/src/lib/helpers/upsertEnvKey.js +61 -0
  105. package/src/lib/main.d.ts +152 -0
  106. package/src/lib/main.js +114 -0
  107. package/src/lib/services/armorDown.js +77 -0
  108. package/src/lib/services/armorMove.js +54 -0
  109. package/src/lib/services/armorPull.js +77 -0
  110. package/src/lib/services/armorPush.js +82 -0
  111. package/src/lib/services/armorUp.js +79 -0
  112. package/src/lib/services/backup.js +105 -0
  113. package/src/lib/services/gatewayStart.js +132 -0
  114. package/src/lib/services/keypair.js +59 -0
  115. package/src/lib/services/loggedIn.js +24 -0
  116. package/src/lib/services/login.js +28 -0
  117. package/src/lib/services/loginPoll.js +43 -0
  118. package/src/lib/services/logout.js +28 -0
  119. package/src/lib/services/rotate.js +28 -0
  120. package/src/lib/services/rotateGithubConnect.js +56 -0
  121. package/src/lib/services/rotateNpmConnect.js +56 -0
  122. package/src/lib/services/rotateOpenaiConnect.js +60 -0
  123. package/src/lib/services/status.js +11 -0
  124. package/src/lib/services/sync.js +70 -0
  125. 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