Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions simple_auth/lib/simple_auth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export 'src/oauth/oauthApi.dart';
export 'src/oauth/oauthApiKeyApi.dart';
export 'src/oauth/oauthAuthenticator.dart';
export 'src/oauth/oauthResponse.dart';
export 'src/oauth/oauthPasswordApi.dart';
export 'src/oauth/oauthPasswordAuthenticator.dart';
export 'src/providers/amazon.dart';
export 'src/providers/azureAD.dart';
export 'src/providers/azureADV2.dart';
Expand Down
13 changes: 13 additions & 0 deletions simple_auth/lib/src/annotations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,19 @@ class OAuthApiDeclaration extends ApiDeclaration {
: super(name, baseUrl: baseUrl);
}

@immutable
class OAuthPasswordApiDeclaration extends ApiDeclaration {
final String clientId;
final String clientSecret;
final String tokenUrl;
final String loginUrl;
const OAuthPasswordApiDeclaration(String name, this.clientId,
this.clientSecret, this.loginUrl , this.tokenUrl,
{String baseUrl = "/"})
: super(name, baseUrl: baseUrl);
}


@immutable
class AmazonApiDeclaration extends ApiDeclaration {
final String clientId;
Expand Down
96 changes: 96 additions & 0 deletions simple_auth/lib/src/oauth/oauthPasswordApi.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import "dart:async";
import "package:simple_auth/simple_auth.dart";
import "package:http/http.dart" as http;
import "dart:convert" as convert;

typedef void ShowOauthPasswordAuthenticator(
OauthPasswordAuthenticator authenticator);

class OAuthPasswordApi extends OAuthApi {
String loginUrl;
String tokenUrl;
OauthPasswordAuthenticator currentAuthenticator;
static ShowOauthPasswordAuthenticator sharedShowAuthenticator;


OAuthPasswordApi(String identifier,
this.loginUrl,
this.tokenUrl,
String clientId,
String clientSecret,
{List<String> scopes,
http.Client client,
Converter converter,
AuthStorage authStorage})
: super.fromIdAndSecret(identifier, clientId, clientSecret,
client: client,
scopes: scopes,
converter: converter,
authStorage: authStorage) {
this.scopesRequired = false;
}

OAuthAccount get currentOauthAccount => currentAccount as OAuthAccount;


@override
Future<Account> performAuthenticate() async {
if (scopesRequired && (scopes?.length ?? 0) == 0) {
throw Exception("Scopes are required");
}
OAuthAccount account =
currentOauthAccount ?? await loadAccountFromCache<OAuthAccount>();
if (account != null &&
((account.refreshToken?.isNotEmpty ?? false) ||
(account.expiresIn != null && account.expiresIn <= 0))) {
var valid = account.isValid();
if (!valid || forceRefresh ?? false) {
//If there is no interent, give them the current expired account
if (!await pingUrl(tokenUrl)) {
return account;
}
if (await refreshAccount(account))
account = currentOauthAccount ?? loadAccountFromCache<OAuthAccount>();
}
if (account.isValid()) {
saveAccountToCache(account);
currentAccount = account;
return account;
}
}

var _authenticator = getAuthenticator();
await _authenticator.resetAuthenticator();
if (sharedShowAuthenticator != null)
sharedShowAuthenticator(_authenticator);
else
throw new Exception(
"You are required to implement the 'showAuthenticator or sharedShowAuthenticator");
var token = await _authenticator.getAuthCode();
if (token?.isEmpty ?? true) {
throw new Exception("Null Token");
}
account = await getAccountFromAuthCode(_authenticator);
saveAccountToCache(account);
currentAccount = account;
return account;
}


@override
OauthPasswordAuthenticator getAuthenticator() => OauthPasswordAuthenticator(identifier, clientId, clientSecret,loginUrl , tokenUrl, baseUrl, redirectUrl, scopes);


@override
Future<OAuthAccount> getAccountFromAuthCode(
WebAuthenticator authenticator) async {
var auth = authenticator as OauthPasswordAuthenticator;
return OAuthAccount(identifier,
created: DateTime.now().toUtc(),
expiresIn: auth.token.expiresIn,
refreshToken: auth.token.refreshToken,
scope: authenticator.scope ?? List<String>(),
tokenType: auth.token.tokenType,
token: auth.token.accessToken);
}
}
75 changes: 75 additions & 0 deletions simple_auth/lib/src/oauth/oauthPasswordAuthenticator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import 'dart:convert';

import "package:simple_auth/simple_auth.dart";
import "package:http/http.dart" as http;
import "dart:async";

class OauthPasswordAuthenticator extends OAuthAuthenticator {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets add an interface for the verifyCredentials. Then we can use it for BasicAuth and OAuthPassword.

String loginUrl;
AuthTokenClass token;

OauthPasswordAuthenticator(String identifier, String clientId, String clientSecret,
this.loginUrl, tokenUrl, String baseUrl, String redirectUrl, List<String> scopes)
: super(identifier, clientId, clientSecret, tokenUrl, baseUrl,
redirectUrl) {}

Future<bool> verifyCredentials(String username, String password) async {
try {
if (username?.isEmpty ?? true) throw new Exception("Invalid Username");
if (password?.isEmpty ?? true) throw new Exception("Invalid Password");

Map<String, dynamic> body = {
'username': username,
'password': password,
'grant_type': "password"
};

var headers = {'Content-Type' : 'application/x-www-form-urlencoded'};
var req = await http.post(loginUrl, body: body , headers: headers,encoding: Encoding.getByName("utf-8"));

var success = req.statusCode >= 200 && req.statusCode < 300;
if (!success) return false;


var token = new AuthTokenClass.fromJson(json.decode(req.body));
if(token.accessToken?.isNotEmpty ?? false) {
this.token = token;
foundAuthCode(token.accessToken);
return true;
}
return false;

} catch (e) {
return false;
}
}


///Gets the data that will be posted to swap the auth code for an auth token
Future<Map<String, dynamic>> getTokenPostData(String clientSecret) async {
var data = {
"grant_type": "password",
"client_id": clientId,
"client_secret": clientSecret
};
return data;
}
}

class AuthTokenClass {
String accessToken;
String refreshToken;
String tokenType;
int expiresIn;

AuthTokenClass({this.accessToken,this.refreshToken,this.tokenType,this.expiresIn});

factory AuthTokenClass.fromJson(Map<String,dynamic> json) {
return AuthTokenClass(
accessToken : json['access_token'],
refreshToken : json['refresh_token'],
tokenType: json['token_type'],
expiresIn: json['expires_in'],
);
}
}
123 changes: 123 additions & 0 deletions simple_auth_flutter/lib/oauth_password_login_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import 'package:flutter/material.dart';
import 'package:simple_auth/simple_auth.dart';

class OauthPasswordLoginPage extends StatefulWidget {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets do one credential entry, So we should merge your changes into https://github.com/Clancey/simple_auth/blob/master/simple_auth_flutter/lib/basic_login_page.dart Have it use the interface. Then both can use the same page, and any other that need username/password.

static String defaultLogo;
final OauthPasswordAuthenticator authenticator;
OauthPasswordLoginPage(this.authenticator);
static String tag = 'grant-password-login-page';
@override
_LoginPageState createState() => new _LoginPageState();
}

class _LoginPageState extends State<OauthPasswordLoginPage> {
final emailController = TextEditingController();
final passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
final logo = Hero(
tag: 'hero',
child: CircleAvatar(
backgroundColor: Colors.transparent,
radius: 48.0,
child: (OauthPasswordLoginPage.defaultLogo?.isEmpty ?? true)
? Icon(Icons.supervised_user_circle)
: Image.asset(OauthPasswordLoginPage.defaultLogo),
),
);

final email = TextFormField(
controller: emailController,
keyboardType: TextInputType.emailAddress,
autofocus: true,
decoration: InputDecoration(
hintText: 'Email',
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),
),
);

final password = TextFormField(
controller: passwordController,
autofocus: false,
obscureText: true,
decoration: InputDecoration(
hintText: 'Password',
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),
),
);

final loginButton = Padding(
padding: EdgeInsets.symmetric(vertical: 16.0),
child: Material(
borderRadius: BorderRadius.circular(30.0),
shadowColor: Colors.lightBlueAccent.shade100,
elevation: 5.0,
child: MaterialButton(
minWidth: 200.0,
height: 42.0,
onPressed: () async {
try {
bool success = await widget.authenticator.verifyCredentials(
emailController.text, passwordController.text);
if (success) Navigator.pop(context);
} catch (ex) {
var alert =
new AlertDialog(content: new Text(ex), actions: <Widget>[
new FlatButton(
child: const Text("Ok"),
onPressed: () {
Navigator.pop(context);
})
]);
showDialog(
context: context, builder: (BuildContext context) => alert);
}
},
color: Colors.lightBlueAccent,
child: Text('Log In', style: TextStyle(color: Colors.white)),
),
),
);

return Scaffold(
appBar: new AppBar(
title: Text(widget.authenticator.title),
actions: widget.authenticator.allowsCancel
? <Widget>[
new IconButton(
icon: new Icon(Icons.cancel),
onPressed: () {
widget.authenticator.cancel();
Navigator.pop(context);
},
)
]
: null,
),
backgroundColor: Colors.white,
body: Center(
child: ListView(
shrinkWrap: true,
padding: EdgeInsets.only(left: 24.0, right: 24.0),
children: <Widget>[
logo,
SizedBox(height: 48.0),
email,
SizedBox(height: 8.0),
password,
SizedBox(height: 24.0),
loginButton,
],
),
),
);
}

@override
void dispose() {
widget.authenticator.cancel();
super.dispose();
}
}
35 changes: 35 additions & 0 deletions simple_auth_flutter_example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ class _MyHomePageState extends State<MyHomePage> {

final simpleAuth.BasicAuthApi basicApi = new simpleAuth.BasicAuthApi(
"github-basic", "https://api.github.com/user");

final simpleAuth.OAuthPasswordApi oauthPasswordApi = new simpleAuth
.OAuthPasswordApi(
"oauth-password",
"loginUrl",
"tokenUrl",
"clientId",
"clientSecret");

final simpleAuth.InstagramApi instagramApi = new simpleAuth.InstagramApi(
"instagram", "clientId", "clientSecret", "redirecturl");

Expand Down Expand Up @@ -311,6 +320,32 @@ class _MyHomePageState extends State<MyHomePage> {
showMessage("Logged out");
},
),
ListTile(
title: Text(
"Oauth Grant Password",
style: Theme.of(context).textTheme.headline,
),
),
ListTile(
leading: Icon(Icons.launch),
title: Text('Login'),
onTap: () async {
try {
var success = await oauthPasswordApi.authenticate();
showMessage("Logged in success: $success");
} catch (e) {
showError(e);
}
},
),
ListTile(
leading: Icon(Icons.delete),
title: Text('Logout'),
onTap: () async {
await oauthPasswordApi.logOut();
showMessage("Logged out");
},
),
ListTile(
title: Text(
"Instagram OAuth",
Expand Down
Loading