[Auth] Allow for configuring a custom server (#726)
## Description Users can now tap on the onboarding screen **7 times** to bring up a page where they can configure the endpoint the app should be connecting to. ![self-host](https://github.com/ente-io/ente/assets/1161789/10f61f6d-0fb3-4f5b-889e-806ca7607525) ## Tests - [x] Verified that production flows are working as expected - [x] Verified that configuring the endpoint to a local instance lets you - [x] Connect to that instance - [x] Create an account - [x] Add a key - [x] Modify a key - [x] Logout and log back in
This commit is contained in:
commit
90bbc54bb5
|
@ -6,6 +6,7 @@ import 'dart:typed_data';
|
|||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:ente_auth/core/constants.dart';
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/events/endpoint_updated_event.dart';
|
||||
import 'package:ente_auth/events/signed_in_event.dart';
|
||||
import 'package:ente_auth/events/signed_out_event.dart';
|
||||
import 'package:ente_auth/models/key_attributes.dart';
|
||||
|
@ -42,6 +43,7 @@ class Configuration {
|
|||
static const userIDKey = "user_id";
|
||||
static const hasMigratedSecureStorageKey = "has_migrated_secure_storage";
|
||||
static const hasOptedForOfflineModeKey = "has_opted_for_offline_mode";
|
||||
static const endPointKey = "endpoint";
|
||||
final List<String> onlineSecureKeys = [
|
||||
keyKey,
|
||||
secretKeyKey,
|
||||
|
@ -317,7 +319,12 @@ class Configuration {
|
|||
}
|
||||
|
||||
String getHttpEndpoint() {
|
||||
return endpoint;
|
||||
return _preferences.getString(endPointKey) ?? endpoint;
|
||||
}
|
||||
|
||||
Future<void> setHttpEndpoint(String endpoint) async {
|
||||
await _preferences.setString(endPointKey, endpoint);
|
||||
Bus.instance.fire(EndpointUpdatedEvent());
|
||||
}
|
||||
|
||||
String? getToken() {
|
||||
|
|
|
@ -2,28 +2,24 @@ import 'dart:io';
|
|||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/constants.dart';
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/events/endpoint_updated_event.dart';
|
||||
import 'package:fk_user_agent/fk_user_agent.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
int kConnectTimeout = 15000;
|
||||
|
||||
class Network {
|
||||
// apiEndpoint points to the Ente server's API endpoint
|
||||
static const apiEndpoint = String.fromEnvironment(
|
||||
"endpoint",
|
||||
defaultValue: kDefaultProductionEndpoint,
|
||||
);
|
||||
late Dio _dio;
|
||||
late Dio _enteDio;
|
||||
|
||||
Future<void> init() async {
|
||||
await FkUserAgent.init();
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
final preferences = await SharedPreferences.getInstance();
|
||||
final endpoint = Configuration.instance.getHttpEndpoint();
|
||||
|
||||
_dio = Dio(
|
||||
BaseOptions(
|
||||
connectTimeout: kConnectTimeout,
|
||||
|
@ -34,10 +30,10 @@ class Network {
|
|||
},
|
||||
),
|
||||
);
|
||||
_dio.interceptors.add(RequestIdInterceptor());
|
||||
|
||||
_enteDio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: apiEndpoint,
|
||||
baseUrl: endpoint,
|
||||
connectTimeout: kConnectTimeout,
|
||||
headers: {
|
||||
HttpHeaders.userAgentHeader: FkUserAgent.userAgent,
|
||||
|
@ -46,7 +42,13 @@ class Network {
|
|||
},
|
||||
),
|
||||
);
|
||||
_enteDio.interceptors.add(EnteRequestInterceptor(preferences, apiEndpoint));
|
||||
_setupInterceptors(endpoint);
|
||||
|
||||
Bus.instance.on<EndpointUpdatedEvent>().listen((event) {
|
||||
final endpoint = Configuration.instance.getHttpEndpoint();
|
||||
_enteDio.options.baseUrl = endpoint;
|
||||
_setupInterceptors(endpoint);
|
||||
});
|
||||
}
|
||||
|
||||
Network._privateConstructor();
|
||||
|
@ -55,34 +57,41 @@ class Network {
|
|||
|
||||
Dio getDio() => _dio;
|
||||
Dio get enteDio => _enteDio;
|
||||
|
||||
void _setupInterceptors(String endpoint) {
|
||||
_dio.interceptors.clear();
|
||||
_dio.interceptors.add(RequestIdInterceptor());
|
||||
|
||||
_enteDio.interceptors.clear();
|
||||
_enteDio.interceptors.add(EnteRequestInterceptor(endpoint));
|
||||
}
|
||||
}
|
||||
|
||||
class RequestIdInterceptor extends Interceptor {
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
// ignore: prefer_const_constructors
|
||||
options.headers.putIfAbsent("x-request-id", () => Uuid().v4().toString());
|
||||
options.headers
|
||||
.putIfAbsent("x-request-id", () => const Uuid().v4().toString());
|
||||
return super.onRequest(options, handler);
|
||||
}
|
||||
}
|
||||
|
||||
class EnteRequestInterceptor extends Interceptor {
|
||||
final SharedPreferences _preferences;
|
||||
final String enteEndpoint;
|
||||
final String endpoint;
|
||||
|
||||
EnteRequestInterceptor(this._preferences, this.enteEndpoint);
|
||||
EnteRequestInterceptor(this.endpoint);
|
||||
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
if (kDebugMode) {
|
||||
assert(
|
||||
options.baseUrl == enteEndpoint,
|
||||
options.baseUrl == endpoint,
|
||||
"interceptor should only be used for API endpoint",
|
||||
);
|
||||
}
|
||||
// ignore: prefer_const_constructors
|
||||
options.headers.putIfAbsent("x-request-id", () => Uuid().v4().toString());
|
||||
final String? tokenValue = _preferences.getString(Configuration.tokenKey);
|
||||
options.headers
|
||||
.putIfAbsent("x-request-id", () => const Uuid().v4().toString());
|
||||
final String? tokenValue = Configuration.instance.getToken();
|
||||
if (tokenValue != null) {
|
||||
options.headers.putIfAbsent("X-Auth-Token", () => tokenValue);
|
||||
}
|
||||
|
|
3
auth/lib/events/endpoint_updated_event.dart
Normal file
3
auth/lib/events/endpoint_updated_event.dart
Normal file
|
@ -0,0 +1,3 @@
|
|||
import 'package:ente_auth/events/event.dart';
|
||||
|
||||
class EndpointUpdatedEvent extends Event {}
|
|
@ -1,43 +1,29 @@
|
|||
import 'package:dio/dio.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/errors.dart';
|
||||
import 'package:ente_auth/core/network.dart';
|
||||
import 'package:ente_auth/models/authenticator/auth_entity.dart';
|
||||
import 'package:ente_auth/models/authenticator/auth_key.dart';
|
||||
|
||||
class AuthenticatorGateway {
|
||||
final Dio _dio;
|
||||
final Configuration _config;
|
||||
late String _basedEndpoint;
|
||||
late Dio _enteDio;
|
||||
|
||||
AuthenticatorGateway(this._dio, this._config) {
|
||||
_basedEndpoint = _config.getHttpEndpoint() + "/authenticator";
|
||||
AuthenticatorGateway() {
|
||||
_enteDio = Network.instance.enteDio;
|
||||
}
|
||||
|
||||
Future<void> createKey(String encKey, String header) async {
|
||||
await _dio.post(
|
||||
_basedEndpoint + "/key",
|
||||
await _enteDio.post(
|
||||
"/authenticator/key",
|
||||
data: {
|
||||
"encryptedKey": encKey,
|
||||
"header": header,
|
||||
},
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<AuthKey> getKey() async {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
_basedEndpoint + "/key",
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
},
|
||||
),
|
||||
);
|
||||
final response = await _enteDio.get("/authenticator/key");
|
||||
return AuthKey.fromMap(response.data);
|
||||
} on DioError catch (e) {
|
||||
if (e.response != null && (e.response!.statusCode ?? 0) == 404) {
|
||||
|
@ -51,17 +37,12 @@ class AuthenticatorGateway {
|
|||
}
|
||||
|
||||
Future<AuthEntity> createEntity(String encryptedData, String header) async {
|
||||
final response = await _dio.post(
|
||||
_basedEndpoint + "/entity",
|
||||
final response = await _enteDio.post(
|
||||
"/authenticator/entity",
|
||||
data: {
|
||||
"encryptedData": encryptedData,
|
||||
"header": header,
|
||||
},
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
},
|
||||
),
|
||||
);
|
||||
return AuthEntity.fromMap(response.data);
|
||||
}
|
||||
|
@ -71,50 +52,35 @@ class AuthenticatorGateway {
|
|||
String encryptedData,
|
||||
String header,
|
||||
) async {
|
||||
await _dio.put(
|
||||
_basedEndpoint + "/entity",
|
||||
await _enteDio.put(
|
||||
"/authenticator/entity",
|
||||
data: {
|
||||
"id": id,
|
||||
"encryptedData": encryptedData,
|
||||
"header": header,
|
||||
},
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> deleteEntity(
|
||||
String id,
|
||||
) async {
|
||||
await _dio.delete(
|
||||
_basedEndpoint + "/entity",
|
||||
await _enteDio.delete(
|
||||
"/authenticator/entity",
|
||||
queryParameters: {
|
||||
"id": id,
|
||||
},
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<AuthEntity>> getDiff(int sinceTime, {int limit = 500}) async {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
_basedEndpoint + "/entity/diff",
|
||||
final response = await _enteDio.get(
|
||||
"/authenticator/entity/diff",
|
||||
queryParameters: {
|
||||
"sinceTime": sinceTime,
|
||||
"limit": limit,
|
||||
},
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
},
|
||||
),
|
||||
);
|
||||
final List<AuthEntity> authEntities = <AuthEntity>[];
|
||||
final diff = response.data["diff"] as List;
|
||||
|
|
|
@ -408,5 +408,12 @@
|
|||
"hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!",
|
||||
"waitingForBrowserRequest": "Waiting for browser request...",
|
||||
"launchPasskeyUrlAgain": "Launch passkey URL again",
|
||||
"passkey": "Passkey"
|
||||
"passkey": "Passkey",
|
||||
"developerSettingsWarning":"Are you sure that you want to modify Developer settings?",
|
||||
"developerSettings": "Developer settings",
|
||||
"serverEndpoint": "Server endpoint",
|
||||
"invalidEndpoint": "Invalid endpoint",
|
||||
"invalidEndpointMessage": "Sorry, the endpoint you entered is invalid. Please enter a valid endpoint and try again.",
|
||||
"endpointUpdatedMessage": "Endpoint updated successfully",
|
||||
"customEndpoint": "Connected to {endpoint}"
|
||||
}
|
|
@ -17,6 +17,8 @@ import 'package:ente_auth/ui/common/gradient_button.dart';
|
|||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/ui/components/models/button_result.dart';
|
||||
import 'package:ente_auth/ui/home_page.dart';
|
||||
import 'package:ente_auth/ui/settings/developer_settings_page.dart';
|
||||
import 'package:ente_auth/ui/settings/developer_settings_widget.dart';
|
||||
import 'package:ente_auth/ui/settings/language_picker.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/navigation_util.dart';
|
||||
|
@ -33,8 +35,12 @@ class OnboardingPage extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _OnboardingPageState extends State<OnboardingPage> {
|
||||
static const kDeveloperModeTapCountThreshold = 7;
|
||||
|
||||
late StreamSubscription<TriggerLogoutEvent> _triggerLogoutEvent;
|
||||
|
||||
int _developerModeTapCount = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_triggerLogoutEvent =
|
||||
|
@ -56,114 +62,142 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
|||
final l10n = context.l10n;
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
|
||||
child: Column(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
kDebugMode
|
||||
? GestureDetector(
|
||||
child: const Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Text("Lang"),
|
||||
),
|
||||
onTap: () async {
|
||||
final locale = await getLocale();
|
||||
routeToPage(
|
||||
context,
|
||||
LanguageSelectorPage(
|
||||
appSupportedLocales,
|
||||
(locale) async {
|
||||
await setLocale(locale);
|
||||
App.setLocale(context, locale);
|
||||
},
|
||||
locale,
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
_developerModeTapCount++;
|
||||
if (_developerModeTapCount >= kDeveloperModeTapCountThreshold) {
|
||||
_developerModeTapCount = 0;
|
||||
final result = await showChoiceDialog(
|
||||
context,
|
||||
title: l10n.developerSettings,
|
||||
firstButtonLabel: l10n.yes,
|
||||
body: l10n.developerSettingsWarning,
|
||||
isDismissible: false,
|
||||
);
|
||||
if (result?.action == ButtonAction.first) {
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return const DeveloperSettingsPage();
|
||||
},
|
||||
),
|
||||
);
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
|
||||
child: Column(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
kDebugMode
|
||||
? GestureDetector(
|
||||
child: const Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Text("Lang"),
|
||||
),
|
||||
onTap: () async {
|
||||
final locale = await getLocale();
|
||||
routeToPage(
|
||||
context,
|
||||
LanguageSelectorPage(
|
||||
appSupportedLocales,
|
||||
(locale) async {
|
||||
await setLocale(locale);
|
||||
App.setLocale(context, locale);
|
||||
},
|
||||
locale,
|
||||
),
|
||||
).then((value) {
|
||||
setState(() {});
|
||||
});
|
||||
},
|
||||
)
|
||||
: const SizedBox(),
|
||||
Image.asset(
|
||||
"assets/sheild-front-gradient.png",
|
||||
width: 200,
|
||||
height: 200,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
"ente",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: 'Montserrat',
|
||||
fontSize: 42,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
"Authenticator",
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
Text(
|
||||
l10n.onBoardingBody,
|
||||
textAlign: TextAlign.center,
|
||||
style:
|
||||
Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||
color: Colors.white38,
|
||||
),
|
||||
).then((value) {
|
||||
setState(() {});
|
||||
});
|
||||
},
|
||||
)
|
||||
: const SizedBox(),
|
||||
Image.asset(
|
||||
"assets/sheild-front-gradient.png",
|
||||
width: 200,
|
||||
height: 200,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
"ente",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: 'Montserrat',
|
||||
fontSize: 42,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 100),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: GradientButton(
|
||||
onTap: _navigateToSignUpPage,
|
||||
text: l10n.newUser,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
"Authenticator",
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
Text(
|
||||
l10n.onBoardingBody,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||
color: Colors.white38,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(20, 12, 20, 0),
|
||||
child: Hero(
|
||||
tag: "log_in",
|
||||
child: ElevatedButton(
|
||||
style: Theme.of(context)
|
||||
.colorScheme
|
||||
.optionalActionButtonStyle,
|
||||
onPressed: _navigateToSignInPage,
|
||||
child: Text(
|
||||
l10n.existingUser,
|
||||
style: const TextStyle(
|
||||
color: Colors.black, // same for both themes
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 100),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: GradientButton(
|
||||
onTap: _navigateToSignUpPage,
|
||||
text: l10n.newUser,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(20, 12, 20, 0),
|
||||
child: Hero(
|
||||
tag: "log_in",
|
||||
child: ElevatedButton(
|
||||
style: Theme.of(context)
|
||||
.colorScheme
|
||||
.optionalActionButtonStyle,
|
||||
onPressed: _navigateToSignInPage,
|
||||
child: Text(
|
||||
l10n.existingUser,
|
||||
style: const TextStyle(
|
||||
color: Colors.black, // same for both themes
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.only(top: 20, bottom: 20),
|
||||
child: GestureDetector(
|
||||
onTap: _optForOfflineMode,
|
||||
child: Center(
|
||||
child: Text(
|
||||
l10n.useOffline,
|
||||
style: body.copyWith(
|
||||
color: Theme.of(context).colorScheme.mutedTextColor,
|
||||
const SizedBox(height: 4),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.only(top: 20, bottom: 20),
|
||||
child: GestureDetector(
|
||||
onTap: _optForOfflineMode,
|
||||
child: Center(
|
||||
child: Text(
|
||||
l10n.useOffline,
|
||||
style: body.copyWith(
|
||||
color:
|
||||
Theme.of(context).colorScheme.mutedTextColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
const DeveloperSettingsWidget(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -5,7 +5,6 @@ import 'dart:math';
|
|||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/errors.dart';
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/core/network.dart';
|
||||
import 'package:ente_auth/events/codes_updated_event.dart';
|
||||
import 'package:ente_auth/events/signed_in_event.dart';
|
||||
import 'package:ente_auth/events/trigger_logout_event.dart';
|
||||
|
@ -26,6 +25,7 @@ enum AccountMode {
|
|||
online,
|
||||
offline,
|
||||
}
|
||||
|
||||
extension on AccountMode {
|
||||
bool get isOnline => this == AccountMode.online;
|
||||
bool get isOffline => this == AccountMode.offline;
|
||||
|
@ -56,7 +56,7 @@ class AuthenticatorService {
|
|||
_prefs = await SharedPreferences.getInstance();
|
||||
_db = AuthenticatorDB.instance;
|
||||
_offlineDb = OfflineAuthenticatorDB.instance;
|
||||
_gateway = AuthenticatorGateway(Network.instance.getDio(), _config);
|
||||
_gateway = AuthenticatorGateway();
|
||||
if (Configuration.instance.hasConfiguredAccount()) {
|
||||
unawaited(onlineSync());
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ class AuthenticatorService {
|
|||
} else {
|
||||
debugPrint("Skipping delete since account mode is offline");
|
||||
}
|
||||
if(accountMode.isOnline) {
|
||||
if (accountMode.isOnline) {
|
||||
await _db.deleteByIDs(generatedIDs: [genID]);
|
||||
} else {
|
||||
await _offlineDb.deleteByIDs(generatedIDs: [genID]);
|
||||
|
@ -163,7 +163,7 @@ class AuthenticatorService {
|
|||
|
||||
Future<bool> onlineSync() async {
|
||||
try {
|
||||
if(getAccountMode().isOffline) {
|
||||
if (getAccountMode().isOffline) {
|
||||
debugPrint("Skipping sync since account mode is offline");
|
||||
return false;
|
||||
}
|
||||
|
@ -253,7 +253,7 @@ class AuthenticatorService {
|
|||
}
|
||||
|
||||
Future<Uint8List> getOrCreateAuthDataKey(AccountMode mode) async {
|
||||
if(mode.isOffline) {
|
||||
if (mode.isOffline) {
|
||||
return _config.getOfflineSecretKey()!;
|
||||
}
|
||||
if (_config.getAuthSecretKey() != null) {
|
||||
|
|
|
@ -7,10 +7,6 @@ class UserStore {
|
|||
late SharedPreferences _preferences;
|
||||
|
||||
static final UserStore instance = UserStore._privateConstructor();
|
||||
static const endpoint = String.fromEnvironment(
|
||||
"endpoint",
|
||||
defaultValue: "https://api.ente.io",
|
||||
);
|
||||
|
||||
Future<void> init() async {
|
||||
_preferences = await SharedPreferences.getInstance();
|
||||
|
|
89
auth/lib/ui/settings/developer_settings_page.dart
Normal file
89
auth/lib/ui/settings/developer_settings_page.dart
Normal file
|
@ -0,0 +1,89 @@
|
|||
import 'package:dio/dio.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/ui/common/gradient_button.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class DeveloperSettingsPage extends StatefulWidget {
|
||||
const DeveloperSettingsPage({super.key});
|
||||
|
||||
@override
|
||||
_DeveloperSettingsPageState createState() => _DeveloperSettingsPageState();
|
||||
}
|
||||
|
||||
class _DeveloperSettingsPageState extends State<DeveloperSettingsPage> {
|
||||
final _logger = Logger('DeveloperSettingsPage');
|
||||
final _urlController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_urlController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_logger.info(
|
||||
"Current endpoint is: " + Configuration.instance.getHttpEndpoint(),
|
||||
);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.l10n.developerSettings),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
TextField(
|
||||
controller: _urlController,
|
||||
decoration: InputDecoration(
|
||||
labelText: context.l10n.serverEndpoint,
|
||||
hintText: Configuration.instance.getHttpEndpoint(),
|
||||
),
|
||||
autofocus: true,
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
GradientButton(
|
||||
onTap: () async {
|
||||
String url = _urlController.text;
|
||||
_logger.info("Entered endpoint: " + url);
|
||||
try {
|
||||
final uri = Uri.parse(url);
|
||||
if ((uri.scheme == "http" || uri.scheme == "https")) {
|
||||
await _ping(url);
|
||||
await Configuration.instance.setHttpEndpoint(url);
|
||||
showToast(context, context.l10n.endpointUpdatedMessage);
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
throw const FormatException();
|
||||
}
|
||||
} catch (e) {
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.l10n.invalidEndpoint,
|
||||
context.l10n.invalidEndpointMessage,
|
||||
);
|
||||
}
|
||||
},
|
||||
text: context.l10n.saveAction,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _ping(String endpoint) async {
|
||||
try {
|
||||
final response = await Dio().get(endpoint + '/ping');
|
||||
if (response.data['message'] != 'pong') {
|
||||
throw Exception('Invalid response');
|
||||
}
|
||||
} catch (e) {
|
||||
throw Exception('Error occurred: $e');
|
||||
}
|
||||
}
|
||||
}
|
27
auth/lib/ui/settings/developer_settings_widget.dart
Normal file
27
auth/lib/ui/settings/developer_settings_widget.dart
Normal file
|
@ -0,0 +1,27 @@
|
|||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/constants.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DeveloperSettingsWidget extends StatelessWidget {
|
||||
const DeveloperSettingsWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final endpoint = Configuration.instance.getHttpEndpoint();
|
||||
if (endpoint != kDefaultProductionEndpoint) {
|
||||
final endpointURI = Uri.parse(endpoint);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 20),
|
||||
child: Text(
|
||||
context.l10n.customEndpoint(
|
||||
endpointURI.host + ":" + endpointURI.port.toString(),
|
||||
),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ import 'package:ente_auth/ui/settings/account_section_widget.dart';
|
|||
import 'package:ente_auth/ui/settings/app_version_widget.dart';
|
||||
import 'package:ente_auth/ui/settings/data/data_section_widget.dart';
|
||||
import 'package:ente_auth/ui/settings/data/export_widget.dart';
|
||||
import 'package:ente_auth/ui/settings/developer_settings_widget.dart';
|
||||
import 'package:ente_auth/ui/settings/general_section_widget.dart';
|
||||
import 'package:ente_auth/ui/settings/security_section_widget.dart';
|
||||
import 'package:ente_auth/ui/settings/social_section_widget.dart';
|
||||
|
@ -149,6 +150,7 @@ class SettingsPage extends StatelessWidget {
|
|||
sectionSpacing,
|
||||
const AboutSectionWidget(),
|
||||
const AppVersionWidget(),
|
||||
const DeveloperSettingsWidget(),
|
||||
const SupportDevWidget(),
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 60),
|
||||
|
|
|
@ -203,7 +203,7 @@
|
|||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0920;
|
||||
LastUpgradeCheck = 1300;
|
||||
LastUpgradeCheck = 1430;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
33CC10EC2044A3C60003C045 = {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1300"
|
||||
LastUpgradeVersion = "1430"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
Loading…
Reference in a new issue