react-native-secreton 2.2.1 → 2.3.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/README.md +31 -6
- package/android/build.gradle +3 -1
- package/android/secreton.gradle +33 -20
- package/android/src/main/java/com/secreton/SecretonModule.kt +84 -10
- package/android/src/main/java/com/secreton/SecretonPackage.kt +1 -1
- package/dist/NodeSecreton.js +5 -1
- package/ios/Secreton.mm +87 -8
- package/lib/module/NativeSecreton.js.map +1 -1
- package/lib/module/NodeSecreton.js +5 -1
- package/lib/module/NodeSecreton.js.map +1 -1
- package/lib/module/index.js +5 -2
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/NativeSecreton.d.ts +2 -1
- package/lib/typescript/src/NativeSecreton.d.ts.map +1 -1
- package/lib/typescript/src/NodeSecreton.d.ts +1 -0
- package/lib/typescript/src/NodeSecreton.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +5 -2
- package/react-native-secreton.podspec +2 -0
- package/src/NativeSecreton.ts +2 -1
- package/src/NodeSecreton.ts +6 -1
- package/src/index.tsx +6 -2
package/README.md
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# react-native-secreton
|
|
2
|
-
|
|
2
|
+
A library for managing **secret environment variables** in React Native apps.
|
|
3
|
+
Supports integration with **Consul** and **Vault** to securely retrieve configurations.
|
|
3
4
|
|
|
4
5
|
## Installation
|
|
5
|
-
```
|
|
6
|
+
```bash
|
|
6
7
|
npm install react-native-secreton
|
|
8
|
+
# or
|
|
9
|
+
yarn add react-native-secreton
|
|
7
10
|
```
|
|
8
11
|
|
|
9
12
|
## Generate secret key
|
|
@@ -15,7 +18,7 @@ openssl rand -hex 32
|
|
|
15
18
|
## Setup
|
|
16
19
|
Link the library:
|
|
17
20
|
```
|
|
18
|
-
$ react-native link react-native-
|
|
21
|
+
$ react-native link react-native-secreton
|
|
19
22
|
```
|
|
20
23
|
|
|
21
24
|
- Manual Link (Android)
|
|
@@ -39,6 +42,30 @@ $ react-native link react-native-config
|
|
|
39
42
|
+ pod 'react-native-secreton', :path => '../node_modules/react-native-secreton'
|
|
40
43
|
```
|
|
41
44
|
|
|
45
|
+
## Usage Examples
|
|
46
|
+
|
|
47
|
+
### Using Consul
|
|
48
|
+
```bash
|
|
49
|
+
export ENV_SECRET_KEY=secret-key-32-bytes
|
|
50
|
+
export FETCH_ENV=consul
|
|
51
|
+
export CONSUL_ADDR=http://consul.mycompany.com:8500
|
|
52
|
+
export CONSUL_PATH=mobile/myapp
|
|
53
|
+
export CONSUL_TOKEN=abcd1234
|
|
54
|
+
|
|
55
|
+
rn-secreton-cli .env
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Using Vault
|
|
59
|
+
```bash
|
|
60
|
+
export ENV_SECRET_KEY=secret-key-32-bytes
|
|
61
|
+
export FETCH_ENV=vault
|
|
62
|
+
export VAULT_ADDR=http://vault.mycompany.com:8200
|
|
63
|
+
export VAULT_PATH=secret/data/mobile/myapp
|
|
64
|
+
export VAULT_TOKEN=abcd1234
|
|
65
|
+
|
|
66
|
+
rn-secreton-cli .env
|
|
67
|
+
```
|
|
68
|
+
|
|
42
69
|
## Native Usage
|
|
43
70
|
|
|
44
71
|
### Android
|
|
@@ -63,8 +90,6 @@ Then in Xcode build settings:
|
|
|
63
90
|
2. Use on native iOS (Objective‑C/Swift)
|
|
64
91
|
```objective‑c
|
|
65
92
|
let apiKey = ProcessInfo.processInfo.environment["GEO_APK_API_KEY"]
|
|
66
|
-
|
|
67
|
-
or
|
|
68
|
-
|
|
93
|
+
# or
|
|
69
94
|
let apiKey = Bundle.main.object(forInfoDictionaryKey: "GEO_APK_API_KEY") as? String
|
|
70
95
|
```
|
package/android/build.gradle
CHANGED
|
@@ -18,9 +18,10 @@ buildscript {
|
|
|
18
18
|
|
|
19
19
|
apply plugin: "com.android.library"
|
|
20
20
|
apply plugin: "kotlin-android"
|
|
21
|
-
|
|
22
21
|
apply plugin: "com.facebook.react"
|
|
23
22
|
|
|
23
|
+
apply from: "secreton.gradle"
|
|
24
|
+
|
|
24
25
|
def getExtOrIntegerDefault(name) {
|
|
25
26
|
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["Secreton_" + name]).toInteger()
|
|
26
27
|
}
|
|
@@ -33,6 +34,7 @@ android {
|
|
|
33
34
|
defaultConfig {
|
|
34
35
|
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
35
36
|
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
37
|
+
buildConfigField "String", "ENV_SECRET_KEY", "\"${getSecretKeyFromEnv(project)}\""
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
buildFeatures {
|
package/android/secreton.gradle
CHANGED
|
@@ -38,18 +38,30 @@ ext.loadEnvFile = { File file ->
|
|
|
38
38
|
return map
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
ext.getEnvFile = { appProject ->
|
|
42
|
+
def androidDir = appProject.rootProject.projectDir
|
|
43
|
+
def workspaceRoot = findWorkspaceRoot(androidDir)
|
|
44
|
+
|
|
45
|
+
def envFileName = appProject.findProperty("ENVFILE") ?: System.getenv("ENVFILE") ?: ".env"
|
|
46
|
+
def envFile = new File(workspaceRoot, envFileName)
|
|
47
|
+
|
|
48
|
+
return envFile
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
ext.getSecretKeyFromEnv = { appProject ->
|
|
52
|
+
def envFile = getEnvFile(appProject)
|
|
53
|
+
def envMap = loadEnvFile(envFile)
|
|
54
|
+
|
|
55
|
+
return envMap["ENV_SECRET_KEY"] ?: ""
|
|
56
|
+
}
|
|
57
|
+
|
|
41
58
|
ext.ensureLocalEnv = { appProject ->
|
|
42
59
|
if (appProject.ext.has("localEnv")) return
|
|
43
60
|
|
|
44
61
|
appProject.ext.localEnv = [:]
|
|
45
62
|
appProject.ext.env = [:]
|
|
46
|
-
|
|
47
|
-
def
|
|
48
|
-
def workspaceRoot = findWorkspaceRoot(androidDir)
|
|
49
|
-
def rootDir = workspaceRoot
|
|
50
|
-
|
|
51
|
-
def envFileName = appProject.findProperty("ENVFILE") ?: System.getenv("ENVFILE") ?: ".env"
|
|
52
|
-
def envFile = new File(rootDir, envFileName)
|
|
63
|
+
|
|
64
|
+
def envFile = getEnvFile(appProject)
|
|
53
65
|
|
|
54
66
|
if (envFile.exists()) {
|
|
55
67
|
appProject.ext.localEnv.putAll(loadEnvFile(envFile))
|
|
@@ -72,23 +84,21 @@ ext.decryptValue = { String encrypted, String secretKey ->
|
|
|
72
84
|
proc.waitFor()
|
|
73
85
|
|
|
74
86
|
if (proc.exitValue() != 0) {
|
|
75
|
-
throw new RuntimeException(proc.err.text)
|
|
87
|
+
throw new RuntimeException("❌ OpenSSL decrypt failed: ${proc.err.text}")
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
def output = proc.in.text.trim()
|
|
91
|
+
if (!output) {
|
|
92
|
+
throw new RuntimeException("❌ Decryption returned empty value")
|
|
76
93
|
}
|
|
77
94
|
|
|
78
|
-
return
|
|
95
|
+
return output
|
|
79
96
|
}
|
|
80
97
|
|
|
81
98
|
ext.loadAndDecryptEnv = { appProject ->
|
|
82
99
|
ensureLocalEnv(appProject)
|
|
83
|
-
|
|
84
|
-
def
|
|
85
|
-
def rootDir = findWorkspaceRoot(androidDir)
|
|
86
|
-
|
|
87
|
-
def envFileName =
|
|
88
|
-
appProject.findProperty("ENVFILE")
|
|
89
|
-
?: System.getenv("ENVFILE")
|
|
90
|
-
?: ".env"
|
|
91
|
-
def envFile = new File(rootDir, envFileName)
|
|
100
|
+
|
|
101
|
+
def envFile = getEnvFile(appProject)
|
|
92
102
|
if (!envFile.exists()) return
|
|
93
103
|
|
|
94
104
|
def secretKey = getSecretKey(appProject)
|
|
@@ -97,8 +107,11 @@ ext.loadAndDecryptEnv = { appProject ->
|
|
|
97
107
|
def envMap = loadEnvFile(envFile)
|
|
98
108
|
envMap.each { k, v ->
|
|
99
109
|
def value = v
|
|
100
|
-
if (v instanceof String && v.startsWith("
|
|
101
|
-
value = decryptValue(v.substring(
|
|
110
|
+
if (v instanceof String && v.startsWith("Secreton:")) {
|
|
111
|
+
value = decryptValue(v.substring(9), secretKey)
|
|
112
|
+
}
|
|
113
|
+
if (value == null) {
|
|
114
|
+
throw new RuntimeException("❌ Failed to resolve env ${k}")
|
|
102
115
|
}
|
|
103
116
|
appProject.ext.env[k] = value
|
|
104
117
|
}
|
|
@@ -2,22 +2,96 @@ package com.secreton
|
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
4
4
|
import com.facebook.react.module.annotations.ReactModule
|
|
5
|
+
import android.util.Base64
|
|
6
|
+
import java.security.SecureRandom
|
|
7
|
+
import javax.crypto.Cipher
|
|
8
|
+
import javax.crypto.SecretKeyFactory
|
|
9
|
+
import javax.crypto.spec.IvParameterSpec
|
|
10
|
+
import javax.crypto.spec.PBEKeySpec
|
|
11
|
+
import javax.crypto.spec.SecretKeySpec
|
|
5
12
|
|
|
6
13
|
@ReactModule(name = SecretonModule.NAME)
|
|
7
|
-
class SecretonModule(
|
|
8
|
-
|
|
14
|
+
class SecretonModule(
|
|
15
|
+
reactContext: ReactApplicationContext
|
|
16
|
+
) : NativeSecretonSpec(reactContext) {
|
|
9
17
|
|
|
10
|
-
|
|
11
|
-
|
|
18
|
+
companion object {
|
|
19
|
+
const val NAME = "Secreton"
|
|
20
|
+
private const val PREFIX = "$NAME:"
|
|
21
|
+
private const val ITERATIONS = 100_000
|
|
12
22
|
}
|
|
13
23
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
override fun
|
|
17
|
-
|
|
24
|
+
override fun getName() = NAME
|
|
25
|
+
|
|
26
|
+
override fun encrypt(value: String): String {
|
|
27
|
+
val encrypted = encryptOpenSSL(value, getSecretKey())
|
|
28
|
+
return PREFIX + encrypted
|
|
18
29
|
}
|
|
19
30
|
|
|
20
|
-
|
|
21
|
-
|
|
31
|
+
override fun decrypt(value: String): String {
|
|
32
|
+
if (!value.startsWith(PREFIX)) return value
|
|
33
|
+
return decryptOpenSSL(value.removePrefix(PREFIX), getSecretKey())
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private fun getSecretKey(): String {
|
|
37
|
+
return BuildConfig.ENV_SECRET_KEY
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private fun encryptOpenSSL(value: String, key: String): String {
|
|
41
|
+
val salt = ByteArray(8)
|
|
42
|
+
SecureRandom().nextBytes(salt)
|
|
43
|
+
|
|
44
|
+
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
|
|
45
|
+
val spec = PBEKeySpec(
|
|
46
|
+
key.toCharArray(),
|
|
47
|
+
salt,
|
|
48
|
+
ITERATIONS,
|
|
49
|
+
(32 + 16) * 8 // key + iv in bits
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
val keyIv = factory.generateSecret(spec).encoded
|
|
53
|
+
val aesKey = SecretKeySpec(keyIv.copyOfRange(0, 32), "AES")
|
|
54
|
+
val iv = IvParameterSpec(keyIv.copyOfRange(32, 48))
|
|
55
|
+
|
|
56
|
+
val cipher = Cipher.getInstance("AES/CBC/PKCS7Padding")
|
|
57
|
+
cipher.init(Cipher.ENCRYPT_MODE, aesKey, iv)
|
|
58
|
+
|
|
59
|
+
val encrypted = cipher.doFinal(value.toByteArray(Charsets.UTF_8))
|
|
60
|
+
|
|
61
|
+
val output = ByteArray(16 + encrypted.size)
|
|
62
|
+
System.arraycopy("Salted__".toByteArray(), 0, output, 0, 8)
|
|
63
|
+
System.arraycopy(salt, 0, output, 8, 8)
|
|
64
|
+
System.arraycopy(encrypted, 0, output, 16, encrypted.size)
|
|
65
|
+
|
|
66
|
+
return Base64.encodeToString(output, Base64.NO_WRAP)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private fun decryptOpenSSL(encrypted: String, key: String): String {
|
|
70
|
+
val decoded = Base64.decode(encrypted, Base64.NO_WRAP)
|
|
71
|
+
|
|
72
|
+
val magic = String(decoded.copyOfRange(0, 8))
|
|
73
|
+
if (magic != "Salted__") {
|
|
74
|
+
throw IllegalArgumentException("Invalid OpenSSL salt header")
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
val salt = decoded.copyOfRange(8, 16)
|
|
78
|
+
val cipherText = decoded.copyOfRange(16, decoded.size)
|
|
79
|
+
|
|
80
|
+
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
|
|
81
|
+
val spec = PBEKeySpec(
|
|
82
|
+
key.toCharArray(),
|
|
83
|
+
salt,
|
|
84
|
+
ITERATIONS,
|
|
85
|
+
(32 + 16) * 8
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
val keyIv = factory.generateSecret(spec).encoded
|
|
89
|
+
val aesKey = SecretKeySpec(keyIv.copyOfRange(0, 32), "AES")
|
|
90
|
+
val iv = IvParameterSpec(keyIv.copyOfRange(32, 48))
|
|
91
|
+
|
|
92
|
+
val cipher = Cipher.getInstance("AES/CBC/PKCS7Padding")
|
|
93
|
+
cipher.init(Cipher.DECRYPT_MODE, aesKey, iv)
|
|
94
|
+
|
|
95
|
+
return String(cipher.doFinal(cipherText), Charsets.UTF_8)
|
|
22
96
|
}
|
|
23
97
|
}
|
package/dist/NodeSecreton.js
CHANGED
|
@@ -5,6 +5,10 @@ export function encrypt(value, key) {
|
|
|
5
5
|
const cmd = `printf "%s" "${value}" | openssl enc -aes-256-cbc -a -A -salt -pbkdf2 -iter 100000 -pass pass:${key}`;
|
|
6
6
|
return execSync(cmd).toString().trim();
|
|
7
7
|
}
|
|
8
|
+
export function decrypt(encrypted, key) {
|
|
9
|
+
const cmd = `printf "%s" "${encrypted}" | openssl enc -aes-256-cbc -a -A -d -salt -pbkdf2 -iter 100000 -pass pass:${key}`;
|
|
10
|
+
return execSync(cmd).toString().trim();
|
|
11
|
+
}
|
|
8
12
|
export function readExistingEnv(envFile) {
|
|
9
13
|
if (!fs.existsSync(envFile))
|
|
10
14
|
return new Set();
|
|
@@ -60,7 +64,7 @@ export async function generateEnv(options) {
|
|
|
60
64
|
}
|
|
61
65
|
const newLines = entries
|
|
62
66
|
.filter(({ key }) => !existingKeys.has(key))
|
|
63
|
-
.map(({ key, value }) => `${key}=
|
|
67
|
+
.map(({ key, value }) => `${key}=Secreton:${encrypt(value, secretKey)}`);
|
|
64
68
|
if (newLines.length === 0) {
|
|
65
69
|
console.log('ℹ️ [Node] Secreton no new env keys to add');
|
|
66
70
|
return;
|
package/ios/Secreton.mm
CHANGED
|
@@ -1,21 +1,100 @@
|
|
|
1
1
|
#import "Secreton.h"
|
|
2
2
|
|
|
3
|
+
#import <CommonCrypto/CommonCrypto.h>
|
|
4
|
+
|
|
3
5
|
@implementation Secreton
|
|
4
|
-
- (NSNumber *)multiply:(double)a b:(double)b {
|
|
5
|
-
NSNumber *result = @(a * b);
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
static NSString * const kPrefix = @"Secreton:";
|
|
8
|
+
|
|
9
|
+
+ (NSString *)moduleName
|
|
10
|
+
{
|
|
11
|
+
return @"Secreton";
|
|
8
12
|
}
|
|
9
13
|
|
|
10
|
-
-
|
|
11
|
-
|
|
14
|
+
#pragma mark - TurboModule methods
|
|
15
|
+
|
|
16
|
+
- (NSString *)encrypt:(NSString *)value
|
|
12
17
|
{
|
|
13
|
-
|
|
18
|
+
NSString *key = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"ENV_SECRET_KEY"];
|
|
19
|
+
if (!key || key.length == 0) {
|
|
20
|
+
return nil;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
NSString *encrypted = [self aes:kCCEncrypt value:value key:key];
|
|
24
|
+
return [kPrefix stringByAppendingString:encrypted];
|
|
14
25
|
}
|
|
15
26
|
|
|
16
|
-
|
|
27
|
+
- (NSString *)decrypt:(NSString *)value
|
|
17
28
|
{
|
|
18
|
-
|
|
29
|
+
if (![value hasPrefix:kPrefix]) {
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
NSString *key = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"ENV_SECRET_KEY"];
|
|
34
|
+
if (!key || key.length == 0) {
|
|
35
|
+
return nil;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
NSString *raw = [value substringFromIndex:kPrefix.length];
|
|
39
|
+
return [self aes:kCCDecrypt value:raw key:key];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#pragma mark - AES core
|
|
43
|
+
|
|
44
|
+
- (NSString *)aes:(CCOperation)operation
|
|
45
|
+
value:(NSString *)value
|
|
46
|
+
key:(NSString *)key
|
|
47
|
+
{
|
|
48
|
+
NSData *data = nil;
|
|
49
|
+
|
|
50
|
+
if (operation == kCCEncrypt) {
|
|
51
|
+
data = [value dataUsingEncoding:NSUTF8StringEncoding];
|
|
52
|
+
} else {
|
|
53
|
+
data = [[NSData alloc] initWithBase64EncodedString:value options:0];
|
|
54
|
+
if (!data) return nil;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Key 32 byte (AES-256)
|
|
58
|
+
NSMutableData *keyData = [NSMutableData dataWithLength:kCCKeySizeAES256];
|
|
59
|
+
NSData *rawKey = [key dataUsingEncoding:NSUTF8StringEncoding];
|
|
60
|
+
memcpy(keyData.mutableBytes, rawKey.bytes, MIN(rawKey.length, kCCKeySizeAES256));
|
|
61
|
+
|
|
62
|
+
size_t outLength = 0;
|
|
63
|
+
NSMutableData *outData = [NSMutableData dataWithLength:data.length + kCCBlockSizeAES128];
|
|
64
|
+
|
|
65
|
+
CCCryptorStatus status = CCCrypt(
|
|
66
|
+
operation,
|
|
67
|
+
kCCAlgorithmAES,
|
|
68
|
+
kCCOptionPKCS7Padding,
|
|
69
|
+
keyData.bytes,
|
|
70
|
+
kCCKeySizeAES256,
|
|
71
|
+
NULL, // IV zero
|
|
72
|
+
data.bytes,
|
|
73
|
+
data.length,
|
|
74
|
+
outData.mutableBytes,
|
|
75
|
+
outData.length,
|
|
76
|
+
&outLength
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
if (status != kCCSuccess) {
|
|
80
|
+
return nil;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
outData.length = outLength;
|
|
84
|
+
|
|
85
|
+
if (operation == kCCEncrypt) {
|
|
86
|
+
return [outData base64EncodedStringWithOptions:0];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return [[NSString alloc] initWithData:outData encoding:NSUTF8StringEncoding];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
#pragma mark - TurboModule binding
|
|
93
|
+
|
|
94
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
95
|
+
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
96
|
+
{
|
|
97
|
+
return std::make_shared<facebook::react::NativeSecretonSpecJSI>(params);
|
|
19
98
|
}
|
|
20
99
|
|
|
21
100
|
@end
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeSecreton.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAA0B,cAAc;
|
|
1
|
+
{"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeSecreton.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAA0B,cAAc;AAOpE,eAAeA,mBAAmB,CAACC,YAAY,CAAO,UAAU,CAAC","ignoreList":[]}
|
|
@@ -7,6 +7,10 @@ export function encrypt(value, key) {
|
|
|
7
7
|
const cmd = `printf "%s" "${value}" | openssl enc -aes-256-cbc -a -A -salt -pbkdf2 -iter 100000 -pass pass:${key}`;
|
|
8
8
|
return execSync(cmd).toString().trim();
|
|
9
9
|
}
|
|
10
|
+
export function decrypt(encrypted, key) {
|
|
11
|
+
const cmd = `printf "%s" "${encrypted}" | openssl enc -aes-256-cbc -a -A -d -salt -pbkdf2 -iter 100000 -pass pass:${key}`;
|
|
12
|
+
return execSync(cmd).toString().trim();
|
|
13
|
+
}
|
|
10
14
|
export function readExistingEnv(envFile) {
|
|
11
15
|
if (!fs.existsSync(envFile)) return new Set();
|
|
12
16
|
const content = fs.readFileSync(envFile, 'utf8');
|
|
@@ -67,7 +71,7 @@ export async function generateEnv(options) {
|
|
|
67
71
|
}) => !existingKeys.has(key)).map(({
|
|
68
72
|
key,
|
|
69
73
|
value
|
|
70
|
-
}) => `${key}=
|
|
74
|
+
}) => `${key}=Secreton:${encrypt(value, secretKey)}`);
|
|
71
75
|
if (newLines.length === 0) {
|
|
72
76
|
console.log('ℹ️ [Node] Secreton no new env keys to add');
|
|
73
77
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["path","fs","execSync","encrypt","value","key","cmd","toString","trim","readExistingEnv","envFile","existsSync","Set","content","readFileSync","split","map","line","filter","Boolean","startsWith","length","fetchConsul","addr","token","headers","res","fetch","ok","Error","data","json","item","Value","Key","replace","Buffer","from","fetchVault","Object","entries","String","generateEnv","options","secretKey","fetchEnv","fileName","basename","existingKeys","vault","consul","newLines","has","console","log","appendFileSync","join"],"sourceRoot":"../../src","sources":["NodeSecreton.ts"],"mappings":";;AAAA,OAAOA,IAAI,MAAM,WAAW;AAC5B,OAAOC,EAAE,MAAM,SAAS;AACxB,SAASC,QAAQ,QAAQ,oBAAoB;AAgC7C,OAAO,SAASC,OAAOA,CAACC,KAAa,EAAEC,GAAW,EAAE;EAClD,MAAMC,GAAG,GAAG,gBAAgBF,KAAK,4EAA4EC,GAAG,EAAE;EAClH,OAAOH,QAAQ,CAACI,GAAG,CAAC,CAACC,QAAQ,CAAC,CAAC,CAACC,IAAI,CAAC,CAAC;AACxC;AAEA,OAAO,SAASC,eAAeA,CAACC,OAAe,EAAe;EAC5D,IAAI,
|
|
1
|
+
{"version":3,"names":["path","fs","execSync","encrypt","value","key","cmd","toString","trim","decrypt","encrypted","readExistingEnv","envFile","existsSync","Set","content","readFileSync","split","map","line","filter","Boolean","startsWith","length","fetchConsul","addr","token","headers","res","fetch","ok","Error","data","json","item","Value","Key","replace","Buffer","from","fetchVault","Object","entries","String","generateEnv","options","secretKey","fetchEnv","fileName","basename","existingKeys","vault","consul","newLines","has","console","log","appendFileSync","join"],"sourceRoot":"../../src","sources":["NodeSecreton.ts"],"mappings":"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","ignoreList":[]}
|
package/lib/module/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import Secreton from "./NativeSecreton.js";
|
|
4
|
-
export function
|
|
5
|
-
return Secreton.
|
|
4
|
+
export function encrypt(value) {
|
|
5
|
+
return Secreton.encrypt(value);
|
|
6
|
+
}
|
|
7
|
+
export function decrypt(encrypted) {
|
|
8
|
+
return Secreton.decrypt(encrypted);
|
|
6
9
|
}
|
|
7
10
|
//# sourceMappingURL=index.js.map
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["Secreton","
|
|
1
|
+
{"version":3,"names":["Secreton","encrypt","value","decrypt","encrypted"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,OAAOA,QAAQ,MAAM,qBAAkB;AAEvC,OAAO,SAASC,OAAOA,CAACC,KAAa,EAAU;EAC7C,OAAOF,QAAQ,CAACC,OAAO,CAACC,KAAK,CAAC;AAChC;AAEA,OAAO,SAASC,OAAOA,CAACC,SAAiB,EAAU;EACjD,OAAOJ,QAAQ,CAACG,OAAO,CAACC,SAAS,CAAC;AACpC","ignoreList":[]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type TurboModule } from 'react-native';
|
|
2
2
|
export interface Spec extends TurboModule {
|
|
3
|
-
|
|
3
|
+
encrypt(value: string): string;
|
|
4
|
+
decrypt(encrypted: string): string;
|
|
4
5
|
}
|
|
5
6
|
declare const _default: Spec;
|
|
6
7
|
export default _default;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NativeSecreton.d.ts","sourceRoot":"","sources":["../../../src/NativeSecreton.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAErE,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,
|
|
1
|
+
{"version":3,"file":"NativeSecreton.d.ts","sourceRoot":"","sources":["../../../src/NativeSecreton.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAErE,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;CACpC;;AAED,wBAAkE"}
|
|
@@ -24,6 +24,7 @@ export interface GenerateEnvOptions {
|
|
|
24
24
|
fetchEnv?: 'consul' | 'vault';
|
|
25
25
|
}
|
|
26
26
|
export declare function encrypt(value: string, key: string): string;
|
|
27
|
+
export declare function decrypt(encrypted: string, key: string): string;
|
|
27
28
|
export declare function readExistingEnv(envFile: string): Set<string>;
|
|
28
29
|
export declare function generateEnv(options: GenerateEnvOptions): Promise<void>;
|
|
29
30
|
//# sourceMappingURL=NodeSecreton.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeSecreton.d.ts","sourceRoot":"","sources":["../../../src/NodeSecreton.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;CAC/B;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,UAGjD;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAc5D;AAwCD,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,iBAgC5D"}
|
|
1
|
+
{"version":3,"file":"NodeSecreton.d.ts","sourceRoot":"","sources":["../../../src/NodeSecreton.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;CAC/B;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,UAGjD;AAED,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,UAGrD;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAc5D;AAwCD,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,iBAgC5D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAEA,wBAAgB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAEA,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEjD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-secreton",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "Config secret variables for React Native apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -48,7 +48,8 @@
|
|
|
48
48
|
"nitrogen": "nitrogen",
|
|
49
49
|
"typecheck": "tsc",
|
|
50
50
|
"lint": "eslint \"src/**/*.{js,ts,tsx}\"",
|
|
51
|
-
"test": "jest",
|
|
51
|
+
"test": "jest -c jest.config.cjs",
|
|
52
|
+
"test:coverage": "jest -c jest.config.cjs --coverage --coverageDirectory=.coverage",
|
|
52
53
|
"release": "release-it --only-version"
|
|
53
54
|
},
|
|
54
55
|
"bin": {
|
|
@@ -76,6 +77,7 @@
|
|
|
76
77
|
"@react-native/eslint-config": "0.83.0",
|
|
77
78
|
"@release-it/conventional-changelog": "^10.0.1",
|
|
78
79
|
"@types/jest": "^29.5.14",
|
|
80
|
+
"@types/node": "^25.0.5",
|
|
79
81
|
"@types/react": "^19.2.0",
|
|
80
82
|
"commitlint": "^19.8.1",
|
|
81
83
|
"del-cli": "^6.0.0",
|
|
@@ -90,6 +92,7 @@
|
|
|
90
92
|
"react-native": "0.83.0",
|
|
91
93
|
"react-native-builder-bob": "^0.40.17",
|
|
92
94
|
"release-it": "^19.0.4",
|
|
95
|
+
"ts-jest": "^29.4.6",
|
|
93
96
|
"turbo": "^2.5.6",
|
|
94
97
|
"typescript": "^5.9.2"
|
|
95
98
|
},
|
package/src/NativeSecreton.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { TurboModuleRegistry, type TurboModule } from 'react-native';
|
|
2
2
|
|
|
3
3
|
export interface Spec extends TurboModule {
|
|
4
|
-
|
|
4
|
+
encrypt(value: string): string;
|
|
5
|
+
decrypt(encrypted: string): string;
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
export default TurboModuleRegistry.getEnforcing<Spec>('Secreton');
|
package/src/NodeSecreton.ts
CHANGED
|
@@ -37,6 +37,11 @@ export function encrypt(value: string, key: string) {
|
|
|
37
37
|
return execSync(cmd).toString().trim();
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
export function decrypt(encrypted: string, key: string) {
|
|
41
|
+
const cmd = `printf "%s" "${encrypted}" | openssl enc -aes-256-cbc -a -A -d -salt -pbkdf2 -iter 100000 -pass pass:${key}`;
|
|
42
|
+
return execSync(cmd).toString().trim();
|
|
43
|
+
}
|
|
44
|
+
|
|
40
45
|
export function readExistingEnv(envFile: string): Set<string> {
|
|
41
46
|
if (!fs.existsSync(envFile)) return new Set();
|
|
42
47
|
|
|
@@ -113,7 +118,7 @@ export async function generateEnv(options: GenerateEnvOptions) {
|
|
|
113
118
|
|
|
114
119
|
const newLines = entries
|
|
115
120
|
.filter(({ key }) => !existingKeys.has(key))
|
|
116
|
-
.map(({ key, value }) => `${key}=
|
|
121
|
+
.map(({ key, value }) => `${key}=Secreton:${encrypt(value, secretKey)}`);
|
|
117
122
|
|
|
118
123
|
if (newLines.length === 0) {
|
|
119
124
|
console.log('ℹ️ [Node] Secreton no new env keys to add');
|
package/src/index.tsx
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import Secreton from './NativeSecreton';
|
|
2
2
|
|
|
3
|
-
export function
|
|
4
|
-
return Secreton.
|
|
3
|
+
export function encrypt(value: string): string {
|
|
4
|
+
return Secreton.encrypt(value);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function decrypt(encrypted: string): string {
|
|
8
|
+
return Secreton.decrypt(encrypted);
|
|
5
9
|
}
|