Skip to content

Commit

Permalink
feat: generate a long lived access_token
Browse files Browse the repository at this point in the history
closes: #9
  • Loading branch information
GioPan04 committed May 9, 2024
1 parent dcdb9db commit bb04ae6
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 32 deletions.
27 changes: 27 additions & 0 deletions lib/api/wakatime.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import 'package:dio/dio.dart';
import 'package:flutterwaka/models/commit.dart';
import 'package:flutterwaka/models/config/wakatime_config.dart';
import 'package:flutterwaka/models/duration.dart';
import 'package:flutterwaka/models/heartbeat.dart';
import 'package:flutterwaka/models/local/account.dart';
import 'package:flutterwaka/models/project.dart';
import 'package:flutterwaka/models/stats.dart';
import 'package:flutterwaka/models/summary.dart';
Expand All @@ -14,6 +16,31 @@ class WakaTimeApi {

final _dateFormat = DateFormat('y-MM-dd');

static Future<Account> getToken(String code) async {
final res = await Dio().post(
'https://wakatime.com/oauth/token',
options: Options(
contentType: Headers.jsonContentType,
headers: {
Headers.acceptHeader: Headers.jsonContentType,
},
),
data: {
'client_id': WakaTimeConfig.appId,
'client_secret': WakaTimeConfig.appSecret,
'redirect_uri': WakaTimeConfig.redirect,
'grant_type': 'authorization_code',
'code': code
},
);

return Account.wakatime(
accessToken: res.data['access_token'],
refreshToken: res.data['refresh_token'],
tokenExpire: DateTime.parse(res.data['expires_at']),
);
}

/// Fetch the user's summary from a given range.
Future<Summary> getSummary(
DateTime start,
Expand Down
13 changes: 13 additions & 0 deletions lib/models/config/wakatime_config.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
abstract class WakaTimeConfig {
static const appId = 'MBzCVc9hyiqz6KKQjKSJZ2tM';
// Shouldn't be a problem posting this publicly...
static const appSecret = String.fromEnvironment('WAKABOARD_CLIENT_SECRET');
static const redirect = 'flutterwaka://auth/redirect';
static const scopes = [
'email',
'read_logged_time',
'read_stats',
'read_orgs',
'read_private_leaderboards'
];
}
4 changes: 4 additions & 0 deletions lib/models/local/account.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ part 'account.g.dart';
@freezed
sealed class Account with _$Account {
const factory Account() = AccountData;

const factory Account.wakatime({
required String accessToken,
required String refreshToken,
required DateTime tokenExpire,
}) = AccountWakatime;

const factory Account.custom({
required String serverUrl,
required String apiKey,
Expand Down
33 changes: 28 additions & 5 deletions lib/pages/login.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutterwaka/api/auth.dart';
import 'package:flutterwaka/api/wakatime.dart';
import 'package:flutterwaka/models/local/account.dart';
import 'package:flutterwaka/models/user.dart';
import 'package:flutterwaka/providers/logged_user.dart';
Expand All @@ -22,13 +23,35 @@ class LoginPage extends ConsumerStatefulWidget {

class LoginPageState extends ConsumerState<LoginPage> {
Future<void> _wakatimeLogin(AuthApi api) async {
final accessToken = await WakaTimeOAuth.launch();
if (accessToken == null) throw Exception('No access token');
try {
final code = await WakaTimeOAuth.launch();
if (code == null) throw Exception('No access token');

final account = await WakaTimeApi.getToken(code);
final user = await api.logon(account);

ref.read(loggedUserProvider.notifier).state = AuthUser(user, account);
} catch (e, s) {
if (!mounted) rethrow;

final account = Account.wakatime(accessToken: accessToken);
final user = await api.logon(account);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('An error occured'),
action: SnackBarAction(
label: "Details",
onPressed: () => showDialog(
context: context,
builder: (context) => ExceptionDialog(
error: e,
stacktrace: s,
),
),
),
),
);

ref.read(loggedUserProvider.notifier).state = AuthUser(user, account);
rethrow;
}
}

Future<void> _customLogin(AuthApi api, String baseUri, String token) async {
Expand Down
34 changes: 7 additions & 27 deletions lib/services/wakatime_oauth.dart
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
import 'package:flutterwaka/models/config/wakatime_config.dart';

class WakaTimeOAuth {
static const appId = 'MBzCVc9hyiqz6KKQjKSJZ2tM';
static const redirect = 'flutterwaka://auth/redirect';
static const scopes = [
'email',
'read_logged_time',
'read_stats',
'read_orgs',
'read_private_leaderboards'
];

static final url = Uri.https(
'wakatime.com',
'/oauth/authorize',
{
'client_id': appId,
'redirect_uri': redirect,
'scope': scopes.join(','),
'response_type': 'token',
'client_id': WakaTimeConfig.appId,
'redirect_uri': WakaTimeConfig.redirect,
'scope': WakaTimeConfig.scopes.join(','),
'response_type': 'code',
},
);

Expand All @@ -30,18 +21,7 @@ class WakaTimeOAuth {
callbackUrlScheme: 'flutterwaka',
);

final params = _parseUri(res, redirect);
return params['access_token'];
}

static Map<String, String> _parseUri(String uri, String redirect) {
final paramList = uri.replaceFirst('$redirect#', '').split('&');
final params = <String, String>{};
for (final p in paramList) {
final param = p.split('=');
params[param[0]] = param[1];
}

return params;
final params = Uri.parse(res);
return params.queryParameters['code'];
}
}

0 comments on commit bb04ae6

Please sign in to comment.