Skip to content

Commit 08b6f82

Browse files
Merge pull request #4 from ThexXTURBOXx/main
feat: Add obfuscation option
2 parents dfc26ac + e5a0057 commit 08b6f82

File tree

5 files changed

+148
-3
lines changed

5 files changed

+148
-3
lines changed

packages/envied/lib/src/envied_base.dart

+19-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,16 @@ class Envied {
2828
/// ```
2929
final String? name;
3030

31-
const Envied({String? path, bool? requireEnvFile, this.name})
31+
/// Allows all the values to be encrypted using a random
32+
/// generated key that is then XOR'd with the encrypted
33+
/// value when being accessed the first time.
34+
/// Please note that the values can not be offered with
35+
/// the const qualifier, but only with final.
36+
/// **Can be overridden by the per-field obfuscate option!**
37+
final bool obfuscate;
38+
39+
const Envied(
40+
{String? path, bool? requireEnvFile, this.name, this.obfuscate = false})
3241
: path = path ?? '.env',
3342
requireEnvFile = requireEnvFile ?? false;
3443
}
@@ -38,5 +47,13 @@ class EnviedField {
3847
/// The environment variable name specified in the `.env` file to generate for the annotated variable
3948
final String? varName;
4049

41-
const EnviedField({this.varName});
50+
/// Allows this values to be encrypted using a random
51+
/// generated key that is then XOR'd with the encrypted
52+
/// value when being accessed the first time.
53+
/// Please note that the values can not be offered with
54+
/// the const qualifier, but only with final.
55+
/// **Overrides the per-class obfuscate option!**
56+
final bool? obfuscate;
57+
58+
const EnviedField({this.varName, this.obfuscate});
4259
}

packages/envied/test/envy_test.dart

+13
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ void main() {
66
test('Empty constructor', () {
77
final envied = Envied();
88
expect(envied.path, '.env');
9+
expect(envied.requireEnvFile, false);
10+
expect(envied.obfuscate, false);
911
});
1012

1113
test('Specified path', () {
@@ -22,17 +24,28 @@ void main() {
2224
final envied = Envied(name: 'Foo');
2325
expect(envied.name, 'Foo');
2426
});
27+
28+
test('Specified obfuscate', () {
29+
final envied = Envied(obfuscate: true);
30+
expect(envied.obfuscate, true);
31+
});
2532
});
2633

2734
group('EnviedField Test Group', () {
2835
test('Empty constructor', () {
2936
final enviedField = EnviedField();
3037
expect(enviedField.varName, null);
38+
expect(enviedField.obfuscate, null);
3139
});
3240

3341
test('Specified path', () {
3442
final enviedField = EnviedField(varName: 'test');
3543
expect(enviedField.varName, 'test');
3644
});
45+
46+
test('Specified obfuscate', () {
47+
final enviedField = EnviedField(obfuscate: true);
48+
expect(enviedField.obfuscate, true);
49+
});
3750
});
3851
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import 'dart:math';
2+
3+
import 'package:analyzer/dart/element/element.dart';
4+
import 'package:source_gen/source_gen.dart';
5+
6+
/// Generate the line to be used in the generated class.
7+
/// If [value] is `null`, it means the variable definition doesn't exist
8+
/// and an [InvalidGenerationSourceError] will be thrown.
9+
///
10+
/// Since this function also does the type casting,
11+
/// an [InvalidGenerationSourceError] will also be thrown if
12+
/// the type can't be casted, or is not supported.
13+
String generateLineEncrypted(FieldElement field, String? value) {
14+
if (value == null) {
15+
throw InvalidGenerationSourceError(
16+
'Environment variable not found for field `${field.name}`.',
17+
element: field,
18+
);
19+
}
20+
21+
final rand = Random.secure();
22+
final type = field.type.getDisplayString(withNullability: false);
23+
final name = field.name;
24+
final keyName = '_enviedkey$name';
25+
26+
switch (type) {
27+
case "int":
28+
final parsed = int.tryParse(value);
29+
if (parsed == null) {
30+
throw InvalidGenerationSourceError(
31+
'Type `$type` does not align up to value `$value`.',
32+
element: field,
33+
);
34+
} else {
35+
final key = rand.nextInt(1 << 32);
36+
final encValue = parsed ^ key;
37+
return 'static final int $keyName = $key;\n'
38+
'static final int $name = $keyName ^ $encValue;';
39+
}
40+
case "bool":
41+
final lowercaseValue = value.toLowerCase();
42+
if (['true', 'false'].contains(lowercaseValue)) {
43+
final parsed = lowercaseValue == 'true';
44+
final key = rand.nextBool();
45+
final encValue = parsed ^ key;
46+
return 'static final bool $keyName = $key;\n'
47+
'static final bool $name = $keyName ^ $encValue;';
48+
} else {
49+
throw InvalidGenerationSourceError(
50+
'Type `$type` does not align up to value `$value`.',
51+
element: field,
52+
);
53+
}
54+
case "String":
55+
case "dynamic":
56+
final parsed = value.codeUnits;
57+
final key = parsed.map((e) => rand.nextInt(1 << 32)).toList(
58+
growable: false,
59+
);
60+
final encValue = List.generate(parsed.length, (i) => i, growable: false)
61+
.map((i) => parsed[i] ^ key[i])
62+
.toList(growable: false);
63+
final encName = '_envieddata$name';
64+
return 'static const List<int> $keyName = [${key.join(", ")}];\n'
65+
'static const List<int> $encName = [${encValue.join(", ")}];\n'
66+
'static final ${type == 'dynamic' ? '' : 'String'} $name = String.fromCharCodes(\n'
67+
' List.generate($encName.length, (i) => i, growable: false)\n'
68+
' .map((i) => $encName[i] ^ $keyName[i])\n'
69+
' .toList(growable: false),\n'
70+
');';
71+
default:
72+
throw InvalidGenerationSourceError(
73+
'Obfuscated envied can only handle types such as `int`, `bool` and `String`. Type `$type` is not one of them.',
74+
element: field,
75+
);
76+
}
77+
}

packages/envied_generator/lib/src/generator.dart

+9-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:analyzer/dart/element/element.dart';
55
import 'package:build/build.dart';
66
import 'package:envied/envied.dart';
77
import 'package:envied_generator/src/generate_line.dart';
8+
import 'package:envied_generator/src/generate_line_encrypted.dart';
89
import 'package:envied_generator/src/load_envs.dart';
910
import 'package:source_gen/source_gen.dart';
1011

@@ -32,6 +33,7 @@ class EnviedGenerator extends GeneratorForAnnotation<Envied> {
3233
requireEnvFile:
3334
annotation.read('requireEnvFile').literalValue as bool? ?? false,
3435
name: annotation.read('name').literalValue as String?,
36+
obfuscate: annotation.read('obfuscate').literalValue as bool,
3537
);
3638

3739
final envs = await loadEnvs(config.path, (error) {
@@ -48,15 +50,21 @@ class EnviedGenerator extends GeneratorForAnnotation<Envied> {
4850
if (enviedFieldChecker.hasAnnotationOf(fieldEl)) {
4951
DartObject? dartObject = enviedFieldChecker.firstAnnotationOf(fieldEl);
5052
ConstantReader reader = ConstantReader(dartObject);
53+
5154
String varName =
5255
reader.read('varName').literalValue as String? ?? fieldEl.name;
56+
5357
String? varValue;
5458
if (envs.containsKey(varName)) {
5559
varValue = envs[varName];
5660
} else if (Platform.environment.containsKey(varName)) {
5761
varValue = Platform.environment[varName];
5862
}
59-
return generateLine(
63+
64+
final bool obfuscate =
65+
reader.read('obfuscate').literalValue as bool? ?? config.obfuscate;
66+
67+
return (obfuscate ? generateLineEncrypted : generateLine)(
6068
fieldEl,
6169
varValue,
6270
);

packages/envied_generator/test/src/generator_tests.dart

+30
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,33 @@ abstract class Env11 {
108108
@EnviedField(varName: 'test_string')
109109
static const String? testString = null;
110110
}
111+
112+
@ShouldGenerate('static const List<int> _enviedkeytestString', contains: true)
113+
@ShouldGenerate('static const List<int> _envieddatatestString', contains: true)
114+
@ShouldGenerate('''
115+
static final String testString = String.fromCharCodes(
116+
List.generate(_envieddatatestString.length, (i) => i, growable: false)
117+
.map((i) => _envieddatatestString[i] ^ _enviedkeytestString[i])
118+
.toList(growable: false),
119+
);
120+
''', contains: true)
121+
@Envied(path: 'test/.env.example', obfuscate: true)
122+
abstract class Env12 {
123+
@EnviedField()
124+
static const String? testString = null;
125+
}
126+
127+
@ShouldGenerate('static const List<int> _enviedkeytestString', contains: true)
128+
@ShouldGenerate('static const List<int> _envieddatatestString', contains: true)
129+
@ShouldGenerate('''
130+
static final String testString = String.fromCharCodes(
131+
List.generate(_envieddatatestString.length, (i) => i, growable: false)
132+
.map((i) => _envieddatatestString[i] ^ _enviedkeytestString[i])
133+
.toList(growable: false),
134+
);
135+
''', contains: true)
136+
@Envied(path: 'test/.env.example', obfuscate: false)
137+
abstract class Env13 {
138+
@EnviedField(obfuscate: true)
139+
static const String? testString = null;
140+
}

0 commit comments

Comments
 (0)