crypta-client 0.1.1
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/.prettierrc +6 -0
- package/.sequelizerc +8 -0
- package/README.md +226 -0
- package/index.js +2 -0
- package/package.json +25 -0
- package/src/assertion.js +19 -0
- package/src/client.js +46 -0
- package/src/errors.js +9 -0
- package/src/http.js +10 -0
- package/src/token.js +40 -0
package/.prettierrc
ADDED
package/.sequelizerc
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# crypta-client
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/crypta-client)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
A Node.js client library for interacting with the Crypta Secret Manager. Securely manage and access secrets using service account authentication with JWT.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- 🔐 **Secure Authentication** - Service account-based authentication using JWT
|
|
11
|
+
- 🔄 **Automatic Token Management** - Built-in token caching and refresh
|
|
12
|
+
- 🚀 **Simple API** - Easy-to-use interface for secret retrieval
|
|
13
|
+
- ⚡ **Lightweight** - Minimal dependencies
|
|
14
|
+
- 🛡️ **Error Handling** - Comprehensive error handling and custom error types
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install crypta-client
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
const { CryptaClient } = require('crypta-client')
|
|
26
|
+
|
|
27
|
+
// Initialize the client
|
|
28
|
+
const client = new CryptaClient({
|
|
29
|
+
baseUrl: 'https://your-crypta-instance.com',
|
|
30
|
+
clientId: 'your-service-account-client-id',
|
|
31
|
+
privateKeyPem: `-----BEGIN PRIVATE KEY-----
|
|
32
|
+
your-private-key-here
|
|
33
|
+
-----END PRIVATE KEY-----`
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
// Get a secret
|
|
37
|
+
async function getMySecret() {
|
|
38
|
+
try {
|
|
39
|
+
const secret = await client.getSecret('my-secret-name')
|
|
40
|
+
console.log('Secret value:', secret.payload)
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error('Error:', error.message)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getMySecret()
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## API Reference
|
|
50
|
+
|
|
51
|
+
### `new CryptaClient(options)`
|
|
52
|
+
|
|
53
|
+
Creates a new Crypta client instance.
|
|
54
|
+
|
|
55
|
+
#### Parameters
|
|
56
|
+
|
|
57
|
+
- `options` (Object)
|
|
58
|
+
- `baseUrl` (string, required) - The base URL of your Crypta instance
|
|
59
|
+
- `clientId` (string, required) - Your service account client ID
|
|
60
|
+
- `privateKeyPem` (string, required) - Your service account private key in PEM format
|
|
61
|
+
|
|
62
|
+
#### Example
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
const client = new CryptaClient({
|
|
66
|
+
baseUrl: 'https://crypta.example.com',
|
|
67
|
+
clientId: 'sa-12345',
|
|
68
|
+
privateKeyPem: process.env.CRYPTA_PRIVATE_KEY
|
|
69
|
+
})
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### `client.getSecret(name)`
|
|
73
|
+
|
|
74
|
+
Retrieves the latest version of a secret.
|
|
75
|
+
|
|
76
|
+
#### Parameters
|
|
77
|
+
|
|
78
|
+
- `name` (string, required) - The name/path of the secret to retrieve
|
|
79
|
+
|
|
80
|
+
#### Returns
|
|
81
|
+
|
|
82
|
+
Returns a Promise that resolves to an object containing:
|
|
83
|
+
|
|
84
|
+
- `payload` (string) - The secret value
|
|
85
|
+
- Additional metadata about the secret
|
|
86
|
+
|
|
87
|
+
#### Example
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
const secret = await client.getSecret('database/password')
|
|
91
|
+
console.log(secret.payload) // The actual secret value
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### Error Handling
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
try {
|
|
98
|
+
const secret = await client.getSecret('my-secret')
|
|
99
|
+
console.log(secret.payload)
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (error.statusCode === 404) {
|
|
102
|
+
console.error('Secret not found')
|
|
103
|
+
} else if (error.statusCode === 401) {
|
|
104
|
+
console.error('Authentication failed')
|
|
105
|
+
} else {
|
|
106
|
+
console.error('Error:', error.message)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Environment Variables
|
|
112
|
+
|
|
113
|
+
It's recommended to store sensitive information in environment variables:
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
require('dotenv').config() // If using dotenv
|
|
117
|
+
|
|
118
|
+
const client = new CryptaClient({
|
|
119
|
+
baseUrl: process.env.CRYPTA_BASE_URL,
|
|
120
|
+
clientId: process.env.CRYPTA_CLIENT_ID,
|
|
121
|
+
privateKeyPem: process.env.CRYPTA_PRIVATE_KEY
|
|
122
|
+
})
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Example `.env` file:
|
|
126
|
+
|
|
127
|
+
```env
|
|
128
|
+
CRYPTA_BASE_URL=https://crypta.example.com
|
|
129
|
+
CRYPTA_CLIENT_ID=sa-12345
|
|
130
|
+
CRYPTA_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Advanced Usage
|
|
134
|
+
|
|
135
|
+
### Token Management
|
|
136
|
+
|
|
137
|
+
The client automatically manages authentication tokens, including caching and refresh. You don't need to handle tokens manually.
|
|
138
|
+
|
|
139
|
+
### Custom Error Handling
|
|
140
|
+
|
|
141
|
+
The library provides custom error types for better error handling:
|
|
142
|
+
|
|
143
|
+
```javascript
|
|
144
|
+
const { CryptaClient } = require('crypta-client')
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const secret = await client.getSecret('my-secret')
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error('Status Code:', error.statusCode)
|
|
150
|
+
console.error('Message:', error.message)
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Multiple Secrets
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
async function getMultipleSecrets() {
|
|
158
|
+
const secretNames = ['db-password', 'api-key', 'encryption-key']
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
const secrets = await Promise.all(
|
|
162
|
+
secretNames.map((name) => client.getSecret(name))
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return secrets.reduce((acc, secret, index) => {
|
|
166
|
+
acc[secretNames[index]] = secret.payload
|
|
167
|
+
return acc
|
|
168
|
+
}, {})
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error('Failed to fetch secrets:', error.message)
|
|
171
|
+
throw error
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Requirements
|
|
177
|
+
|
|
178
|
+
- Node.js >= 14.x
|
|
179
|
+
- A Crypta Secret Manager instance
|
|
180
|
+
- A service account with appropriate permissions
|
|
181
|
+
|
|
182
|
+
## Dependencies
|
|
183
|
+
|
|
184
|
+
- `axios` - HTTP client
|
|
185
|
+
- `jsonwebtoken` - JWT token generation
|
|
186
|
+
|
|
187
|
+
## Error Codes
|
|
188
|
+
|
|
189
|
+
| Status Code | Description |
|
|
190
|
+
| ----------- | ------------------------------------------- |
|
|
191
|
+
| 401 | Authentication failed - Invalid credentials |
|
|
192
|
+
| 403 | Forbidden - Insufficient permissions |
|
|
193
|
+
| 404 | Secret not found |
|
|
194
|
+
| 500 | Internal server error |
|
|
195
|
+
|
|
196
|
+
## Contributing
|
|
197
|
+
|
|
198
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
203
|
+
|
|
204
|
+
## Support
|
|
205
|
+
|
|
206
|
+
For issues and questions:
|
|
207
|
+
|
|
208
|
+
- Open an issue on [GitHub](https://github.com/rayhanzz772/crypta-client/issues)
|
|
209
|
+
- Contact: [Your contact information]
|
|
210
|
+
|
|
211
|
+
## Changelog
|
|
212
|
+
|
|
213
|
+
### v0.1.0
|
|
214
|
+
|
|
215
|
+
- Initial release
|
|
216
|
+
- Service account authentication
|
|
217
|
+
- Secret retrieval functionality
|
|
218
|
+
- Automatic token management
|
|
219
|
+
|
|
220
|
+
## Author
|
|
221
|
+
|
|
222
|
+
Rayhan
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
**Note**: Make sure to keep your private keys secure and never commit them to version control.
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "crypta-client",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Crypta Secret Manager client library for Node.js",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": "Rayhan",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/yourusername/crypta-client"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"secret-manager",
|
|
15
|
+
"vault",
|
|
16
|
+
"cryptography",
|
|
17
|
+
"security",
|
|
18
|
+
"jwt",
|
|
19
|
+
"service-account"
|
|
20
|
+
],
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"axios": "^1.7.0",
|
|
23
|
+
"jsonwebtoken": "^9.0.2"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/assertion.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const jwt = require('jsonwebtoken')
|
|
2
|
+
|
|
3
|
+
function generateAssertion({ clientId, privateKeyPem, audience }) {
|
|
4
|
+
const now = Math.floor(Date.now() / 1000)
|
|
5
|
+
|
|
6
|
+
return jwt.sign(
|
|
7
|
+
{
|
|
8
|
+
iss: clientId,
|
|
9
|
+
sub: clientId,
|
|
10
|
+
aud: audience,
|
|
11
|
+
iat: now,
|
|
12
|
+
exp: now + 120 // 2 menit
|
|
13
|
+
},
|
|
14
|
+
privateKeyPem,
|
|
15
|
+
{ algorithm: 'RS256' }
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = { generateAssertion }
|
package/src/client.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const { createHttp } = require('./http')
|
|
2
|
+
const { TokenManager } = require('./token')
|
|
3
|
+
const { CryptaError } = require('./errors')
|
|
4
|
+
|
|
5
|
+
class CryptaClient {
|
|
6
|
+
constructor({ baseUrl, clientId, privateKeyPem }) {
|
|
7
|
+
if (!baseUrl || !clientId || !privateKeyPem) {
|
|
8
|
+
throw new Error('baseUrl, clientId, dan privateKeyPem wajib diisi')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
this.baseUrl = baseUrl.replace(/\/+$/, '')
|
|
12
|
+
this.clientId = clientId
|
|
13
|
+
this.privateKeyPem = privateKeyPem
|
|
14
|
+
|
|
15
|
+
this.http = createHttp(this.baseUrl)
|
|
16
|
+
|
|
17
|
+
this.tokenManager = new TokenManager({
|
|
18
|
+
http: this.http,
|
|
19
|
+
clientId: this.clientId,
|
|
20
|
+
privateKeyPem: this.privateKeyPem,
|
|
21
|
+
audience: `${this.baseUrl}/public-api/auth/token`
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async getSecret(name) {
|
|
26
|
+
if (!name) throw new Error('secret name wajib diisi')
|
|
27
|
+
|
|
28
|
+
const token = await this.tokenManager.getToken()
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const res = await this.http.get(
|
|
32
|
+
`/v1/secrets/${name}/versions/latest:access`,
|
|
33
|
+
{
|
|
34
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
return res.data.data
|
|
38
|
+
} catch (err) {
|
|
39
|
+
const status = err.response?.status
|
|
40
|
+
const message = err.response?.data?.message || 'Failed to get secret'
|
|
41
|
+
throw new CryptaError(message, status)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = CryptaClient
|
package/src/errors.js
ADDED
package/src/http.js
ADDED
package/src/token.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const { generateAssertion } = require('./assertion')
|
|
2
|
+
|
|
3
|
+
class TokenManager {
|
|
4
|
+
constructor({ http, clientId, privateKeyPem, audience }) {
|
|
5
|
+
this.http = http
|
|
6
|
+
this.clientId = clientId
|
|
7
|
+
this.privateKeyPem = privateKeyPem
|
|
8
|
+
this.audience = audience
|
|
9
|
+
|
|
10
|
+
this.accessToken = null
|
|
11
|
+
this.expiresAt = 0
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
isExpired() {
|
|
15
|
+
return !this.accessToken || Date.now() >= this.expiresAt
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async fetchToken() {
|
|
19
|
+
const assertion = generateAssertion({
|
|
20
|
+
clientId: this.clientId,
|
|
21
|
+
privateKeyPem: this.privateKeyPem,
|
|
22
|
+
audience: this.audience
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const res = await this.http.post('/public-api/auth/token', { assertion })
|
|
26
|
+
|
|
27
|
+
this.accessToken = res.data.access_token
|
|
28
|
+
// 1 menit buffer
|
|
29
|
+
this.expiresAt = Date.now() + 9 * 60 * 1000
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async getToken() {
|
|
33
|
+
if (this.isExpired()) {
|
|
34
|
+
await this.fetchToken()
|
|
35
|
+
}
|
|
36
|
+
return this.accessToken
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { TokenManager }
|