refactor: use envied instead of flutter_dotenv to ensure better security

This commit is contained in:
Kingkor Roy Tirtho 2023-04-26 12:13:42 +06:00
parent 3a6caeb35d
commit 10f1c675d0
12 changed files with 67 additions and 310 deletions

View File

@ -1,3 +1,7 @@
POCKETBASE_URL=
USERNAME=
PASSWORD=
PASSWORD=
# The format:
# SPOTIFY_SECRETS=clintId1:clientSecret1,clientId2:clientSecret2
SPOTIFY_SECRETS=

4
.gitignore vendored
View File

@ -66,7 +66,7 @@ installer.exe
# secrets
*.env
lib/models/generated_secrets.dart
lib/collections/env.g.dart
help.txt
secrets.json
@ -76,4 +76,4 @@ appimage-build
android/key.properties
.fvm/flutter_sdk
**/pb_data
**/pb_data

View File

@ -133,35 +133,15 @@ Do the following:
```bash
dnf install libsecret libsecret-devel jsoncpp gstreamer1-devel gstreamer1-plugins-base-tools gstreamer1-doc gstreamer1-plugins-base-devel gstreamer1-plugins-good gstreamer1-plugins-good-extras
```
- Clone the Repo & Run `flutter pub get` in the Terminal
- Create a `secrets.json` in root of the project. The structure should be similar to the following example:
```jsoc name="secrets.json"
{
"LYRICS_SECRET": [
"Bo3LQEMcL2xUAJ6yCfQowV6f8K78s9J9FLa67AsyWmvhkP9LWikkgcEyFrzvs7jsR",
"HiLHxLj8uv2VhBZfq9BQ9HVrWQk5Jc8aneMZX8RV4KjTmC387K692xrbNK35c8Qe4"
],
"SPOTIFY_SECRET": [
{
"clientId": "9ed19daf-c7a2-4c28-91ac-2c5283ad86cf",
"clientSecret": "236d5822-820e-457e-b18c-10e258c9386b"
},
{
"clientId": "b4769027-e048-4485-8f0b-b8a336f2cd97",
"clientSecret": "41df6ea4-eba2-4d42-b7be-6f727555fccc"
}
]
}
```
> You can add more clientId/clientSecret/genius-access-token if you want. The credentials used in the example are dummy (fake). You've to use your own secrets
- Clone the Repo
- Create a `.env` in root of the project following the `.env.example` template
- Now run the following to bootstrap the project
```bash
flutter pub get && flutter pub run build_runner build --delete-conflicting-outputs
```
- Finally run these following commands in the root of the project to start the Spotube Locally
```bash
$ dart bin/create-secrets.dart --local
$ flutter run -d <window|macos|linux|(<android-device-id>)>
```
```bash
flutter run -d <window|macos|linux|(<android-device-id>)>
```
Do debugging/testing/build etc then submit to us with PR against the development branch (master) & we'll review your code

View File

@ -1,177 +0,0 @@
import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as path;
// blob metadata for de-stringifying
const randHash = [
49,
111,
98,
72,
78,
122,
98,
48,
112,
73,
81,
50,
112,
89,
90,
50,
116,
83,
84,
110,
99,
105,
76,
67,
74,
67,
89,
121,
48,
119,
77,
106,
69,
50,
86,
69,
53,
107,
77,
69,
86,
71,
101,
68,
66,
113,
78,
110,
66,
119
];
const sugarCarbonator = [
81,
119,
79,
71,
85,
53,
78,
50,
69,
52,
90,
68,
107,
120,
77,
87,
89,
52,
89,
84,
73
];
const randomSalt = [
70,
117,
67,
75,
117,
116,
72,
101,
105,
102,
65,
110,
68,
87,
72,
97,
84,
85,
82,
100,
79,
73,
110,
103,
83,
117,
75,
115
];
const algorithmicSugar = [
70,
117,
67,
75,
117,
116,
72,
101,
105,
102,
65,
78,
100,
102,
68,
114,
79,
105,
100,
115,
85,
99,
107,
83
];
void main(List<String> args) async {
List<String> val;
List<Map> val2;
final cwd = Directory.current.path;
final binSafe = cwd.endsWith("/bin") ? ".." : "";
if (args.isEmpty) {
throw ArgumentError("Expected 1-3 arguments but passed none");
}
if (args.contains("--local")) {
final secretFilePath = path.join(cwd, binSafe, "secrets.json");
final file = File(secretFilePath);
if (!file.existsSync()) throw Exception("secrets.json file not found");
final data = jsonDecode(await file.readAsString());
val = List.castFrom<dynamic, String>(data["LYRICS_SECRET"]);
val2 = List.castFrom<dynamic, Map>(data["SPOTIFY_SECRET"]);
} else if (args.contains("--fdroid")) {
final decodedLyricSecret = utf8.decode(base64Decode(
args[1].replaceAll(
String.fromCharCodes(randomSalt), String.fromCharCodes(randHash)),
));
final decodedSpotifySecret = utf8.decode(base64Decode(
args.last.replaceAll(String.fromCharCodes(algorithmicSugar),
String.fromCharCodes(sugarCarbonator)),
));
val = List.castFrom<dynamic, String>(jsonDecode(decodedLyricSecret));
val2 = List.castFrom<dynamic, Map>(jsonDecode(decodedSpotifySecret));
} else {
final decodedLyricSecret = utf8.decode(base64Decode(args.first));
final decodedSpotifySecret = utf8.decode(base64Decode(args.last));
val = List.castFrom<dynamic, String>(jsonDecode(decodedLyricSecret));
val2 = List.castFrom<dynamic, Map>(jsonDecode(decodedSpotifySecret));
}
await File(path.join(cwd, binSafe, "lib/models/generated_secrets.dart"))
.writeAsString(
"final List<String> lyricsSecrets = ${jsonEncode(val)};\nfinal List<Map<String, dynamic>> spotifySecrets = ${jsonEncode(val2)};",
);
}

View File

@ -5,11 +5,9 @@
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal
// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use
import 'package:flutter/widgets.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter/services.dart';
class $AssetsTutorialGen {
const $AssetsTutorialGen();
@ -39,8 +37,7 @@ class Assets {
AssetGenImage('assets/spotube-logo-foreground.jpg');
static const AssetGenImage spotubeLogoPng =
AssetGenImage('assets/spotube-logo.png');
static const SvgGenImage spotubeLogoSvg =
SvgGenImage('assets/spotube-logo.svg');
static const String spotubeLogoSvg = 'assets/spotube-logo.svg';
static const AssetGenImage spotubeScreenshot =
AssetGenImage('assets/spotube-screenshot.jpg');
static const AssetGenImage spotubeBanner =
@ -129,54 +126,3 @@ class AssetGenImage {
String get keyName => _assetName;
}
class SvgGenImage {
const SvgGenImage(this._assetName);
final String _assetName;
SvgPicture svg({
Key? key,
bool matchTextDirection = false,
AssetBundle? bundle,
String? package,
double? width,
double? height,
BoxFit fit = BoxFit.contain,
AlignmentGeometry alignment = Alignment.center,
bool allowDrawingOutsideViewBox = false,
WidgetBuilder? placeholderBuilder,
Color? color,
BlendMode colorBlendMode = BlendMode.srcIn,
String? semanticsLabel,
bool excludeFromSemantics = false,
Clip clipBehavior = Clip.hardEdge,
bool cacheColorFilter = false,
SvgTheme? theme,
}) {
return SvgPicture.asset(
_assetName,
key: key,
matchTextDirection: matchTextDirection,
bundle: bundle,
package: package,
width: width,
height: height,
fit: fit,
alignment: alignment,
allowDrawingOutsideViewBox: allowDrawingOutsideViewBox,
placeholderBuilder: placeholderBuilder,
color: color,
colorBlendMode: colorBlendMode,
semanticsLabel: semanticsLabel,
excludeFromSemantics: excludeFromSemantics,
clipBehavior: clipBehavior,
cacheColorFilter: cacheColorFilter,
theme: theme,
);
}
String get path => _assetName;
String get keyName => _assetName;
}

View File

@ -1,17 +1,24 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:envied/envied.dart';
part 'env.g.dart';
@Envied(obfuscate: true, requireEnvFile: true, path: ".env")
abstract class Env {
static final String pocketbaseUrl =
dotenv.get('POCKETBASE_URL', fallback: 'http://127.0.0.1:8090');
static final String username = dotenv.get('USERNAME', fallback: 'root');
static final String password = dotenv.get('PASSWORD', fallback: '12345678');
@EnviedField(varName: 'POCKETBASE_URL', defaultValue: 'http://127.0.0.1:8090')
static final pocketbaseUrl = _Env.pocketbaseUrl;
static configure() async {
if (kReleaseMode) {
await dotenv.load(fileName: ".env");
} else {
dotenv.testLoad();
}
}
@EnviedField(varName: 'USERNAME', defaultValue: 'root')
static final username = _Env.username;
@EnviedField(varName: 'PASSWORD', defaultValue: '12345678')
static final password = _Env.password;
@EnviedField(varName: 'SPOTIFY_SECRETS')
static final spotifySecrets = _Env.spotifySecrets.split(',').map((e) {
final secrets = e.trim().split(":").map((e) => e.trim());
return {
"clientId": secrets.first,
"clientSecret": secrets.last,
};
}).toList();
}

View File

@ -15,7 +15,6 @@ import 'package:package_info_plus/package_info_plus.dart';
import 'package:platform_ui/platform_ui.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:spotube/collections/cache_keys.dart';
import 'package:spotube/collections/env.dart';
import 'package:spotube/components/shared/dialogs/replace_downloaded_dialog.dart';
import 'package:spotube/entities/cache_track.dart';
import 'package:spotube/collections/routes.dart';
@ -80,7 +79,6 @@ void main(List<String> rawArgs) async {
Hive.registerAdapter(CacheTrackAdapter());
Hive.registerAdapter(CacheTrackEngagementAdapter());
Hive.registerAdapter(CacheTrackSkipSegmentAdapter());
await Env.configure();
if (kIsDesktop) {
await windowManager.ensureInitialized();

View File

@ -1,12 +1,13 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/models/generated_secrets.dart';
import 'package:spotube/collections/env.dart';
import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/utils/primitive_utils.dart';
final spotifyProvider = Provider<SpotifyApi>((ref) {
final authState = ref.watch(AuthenticationNotifier.provider);
final anonCred = PrimitiveUtils.getRandomElement(spotifySecrets);
final anonCred = PrimitiveUtils.getRandomElement(Env.spotifySecrets);
if (authState == null) {
return SpotifyApi(

View File

@ -4,11 +4,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:path_provider/path_provider.dart';
import 'package:spotube/components/settings/color_scheme_picker_dialog.dart';
import 'package:spotube/models/generated_secrets.dart';
import 'package:spotube/utils/persisted_change_notifier.dart';
import 'package:collection/collection.dart';
import 'package:spotube/utils/platform.dart';
import 'package:spotube/utils/primitive_utils.dart';
import 'package:path/path.dart' as path;
enum LayoutMode {
@ -26,7 +25,6 @@ class UserPreferences extends PersistedChangeNotifier {
ThemeMode themeMode;
String recommendationMarket;
bool saveTrackLyrics;
String geniusAccessToken;
bool checkUpdate;
AudioQuality audioQuality;
@ -41,7 +39,6 @@ class UserPreferences extends PersistedChangeNotifier {
bool predownload;
UserPreferences({
required this.geniusAccessToken,
required this.recommendationMarket,
required this.themeMode,
required this.layoutMode,
@ -87,12 +84,6 @@ class UserPreferences extends PersistedChangeNotifier {
updatePersistence();
}
void setGeniusAccessToken(String token) {
geniusAccessToken = token;
notifyListeners();
updatePersistence();
}
void setAccentColorScheme(MaterialColor color) {
accentColorScheme = color;
notifyListeners();
@ -158,8 +149,6 @@ class UserPreferences extends PersistedChangeNotifier {
saveTrackLyrics = map["saveTrackLyrics"] ?? false;
recommendationMarket = map["recommendationMarket"] ?? recommendationMarket;
checkUpdate = map["checkUpdate"] ?? checkUpdate;
geniusAccessToken = map["geniusAccessToken"] ??
PrimitiveUtils.getRandomElement(lyricsSecrets);
themeMode = ThemeMode.values[map["themeMode"] ?? 0];
accentColorScheme = colorsMap.values
@ -185,7 +174,6 @@ class UserPreferences extends PersistedChangeNotifier {
return {
"saveTrackLyrics": saveTrackLyrics,
"recommendationMarket": recommendationMarket,
"geniusAccessToken": geniusAccessToken,
"themeMode": themeMode.index,
"accentColorScheme": accentColorScheme.value,
"checkUpdate": checkUpdate,
@ -201,7 +189,6 @@ class UserPreferences extends PersistedChangeNotifier {
final userPreferencesProvider = ChangeNotifierProvider(
(_) => UserPreferences(
geniusAccessToken: "",
recommendationMarket: 'US',
themeMode: ThemeMode.system,
layoutMode: kIsMobile ? LayoutMode.compact : LayoutMode.adaptive,

View File

@ -9,7 +9,7 @@ import 'package:spotube/models/logger.dart';
import 'package:http/http.dart' as http;
import 'package:spotube/models/lyrics.dart';
import 'package:spotube/models/spotube_track.dart';
import 'package:spotube/models/generated_secrets.dart';
import 'package:spotube/utils/primitive_utils.dart';
import 'package:collection/collection.dart';
import 'package:html/parser.dart' as parser;
@ -23,6 +23,7 @@ abstract class ServiceUtils {
.trim();
}
@Deprecated("In favor spotify lyrics api, this isn't needed anymore")
static String getTitle(
String title, {
List<String> artists = const [],
@ -77,6 +78,7 @@ abstract class ServiceUtils {
return lyrics;
}
@Deprecated("In favor spotify lyrics api, this isn't needed anymore")
static Future<List?> searchSong(
String title,
List<String> artist, {
@ -85,7 +87,7 @@ abstract class ServiceUtils {
bool authHeader = false,
}) async {
if (apiKey == "" || apiKey == null) {
apiKey = PrimitiveUtils.getRandomElement(lyricsSecrets);
apiKey = PrimitiveUtils.getRandomElement(/* lyricsSecrets */ []);
}
const searchUrl = 'https://api.genius.com/search?q=';
String song =
@ -111,6 +113,7 @@ abstract class ServiceUtils {
return results;
}
@Deprecated("In favor spotify lyrics api, this isn't needed anymore")
static Future<String?> getLyrics(
String title,
List<String> artists, {
@ -158,8 +161,10 @@ abstract class ServiceUtils {
return lyrics;
}
@Deprecated("In favor spotify lyrics api, this isn't needed anymore")
static const baseUri = "https://www.rentanadviser.com/subtitles";
@Deprecated("In favor spotify lyrics api, this isn't needed anymore")
static Future<SubtitleSimple?> getTimedLyrics(SpotubeTrack track) async {
final artistNames =
track.artists?.map((artist) => artist.name!).toList() ?? [];

View File

@ -482,6 +482,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.0"
envied:
dependency: "direct main"
description:
name: envied
sha256: d5d978fbd578b5c00123003609c39185e0b1ddf9d2ac460d710dd0eb2fc223d7
url: "https://pub.dev"
source: hosted
version: "0.3.0"
envied_generator:
dependency: "direct dev"
description:
name: envied_generator
sha256: "6c5a98c27c5eae925807692eb252ccac2b8e81f09bace1f07207c47dfb6a4eb0"
url: "https://pub.dev"
source: hosted
version: "0.3.0"
fading_edge_scrollview:
dependency: transitive
description:
@ -607,14 +623,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.0.2"
flutter_dotenv:
dependency: "direct main"
description:
name: flutter_dotenv
sha256: d9283d92059a22e9834bc0a31336658ffba77089fb6f3cc36751f1fc7c6661a3
url: "https://pub.dev"
source: hosted
version: "5.0.2"
flutter_feather_icons:
dependency: "direct main"
description:

View File

@ -24,6 +24,7 @@ dependencies:
collection: ^1.15.0
cupertino_icons: ^1.0.5
dbus: ^0.7.8
envied: ^0.3.0
file_picker: ^5.2.2
fl_query: ^1.0.0-alpha.2
fl_query_hooks: ^1.0.0-alpha.2
@ -32,7 +33,6 @@ dependencies:
flutter:
sdk: flutter
flutter_cache_manager: ^3.3.0
flutter_dotenv: ^5.0.2
flutter_feather_icons: ^2.0.0+1
flutter_hooks: ^0.18.2+1
flutter_inappwebview: ^5.7.2+3
@ -89,6 +89,7 @@ dependencies:
dev_dependencies:
build_runner: ^2.3.2
envied_generator: ^0.3.0
flutter_distributor: ^0.0.2
flutter_gen_runner: ^5.1.0+1
flutter_launcher_icons: ^0.11.0
@ -107,7 +108,6 @@ flutter:
assets:
- assets/
- assets/tutorial/
- .env
flutter_icons:
android: true
@ -124,5 +124,3 @@ flutter_icons:
flutter_gen:
output: lib/collections
integrations:
flutter_svg: true