update: updating files with detailed comments
This commit is contained in:
parent
e7f8083b25
commit
2cb685578b
12 changed files with 2153 additions and 238 deletions
|
@ -7,9 +7,10 @@
|
||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
library;
|
import 'package:protevus_auth/auth.dart';
|
||||||
|
import 'package:protevus_hashing/hashing.dart';
|
||||||
|
import 'package:crypto/crypto.dart';
|
||||||
|
|
||||||
export 'src/auth.dart';
|
|
||||||
export 'src/auth_code_controller.dart';
|
export 'src/auth_code_controller.dart';
|
||||||
export 'src/auth_controller.dart';
|
export 'src/auth_controller.dart';
|
||||||
export 'src/auth_redirect_controller.dart';
|
export 'src/auth_redirect_controller.dart';
|
||||||
|
@ -20,3 +21,60 @@ export 'src/exceptions.dart';
|
||||||
export 'src/objects.dart';
|
export 'src/objects.dart';
|
||||||
export 'src/protocols.dart';
|
export 'src/protocols.dart';
|
||||||
export 'src/validator.dart';
|
export 'src/validator.dart';
|
||||||
|
|
||||||
|
/// A utility method to generate a password hash using the PBKDF2 scheme.
|
||||||
|
///
|
||||||
|
/// This function takes a password and salt as input and generates a secure hash
|
||||||
|
/// using the PBKDF2 (Password-Based Key Derivation Function 2) algorithm.
|
||||||
|
String generatePasswordHash(
|
||||||
|
String password,
|
||||||
|
String salt, {
|
||||||
|
int hashRounds = 1000,
|
||||||
|
int hashLength = 32,
|
||||||
|
Hash? hashFunction,
|
||||||
|
}) {
|
||||||
|
final generator = PBKDF2(hashAlgorithm: hashFunction ?? sha256);
|
||||||
|
return generator.generateBase64Key(password, salt, hashRounds, hashLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A utility method to generate a random base64 salt.
|
||||||
|
///
|
||||||
|
/// This function generates a random salt encoded as a base64 string.
|
||||||
|
/// The salt is useful for adding randomness to password hashing processes,
|
||||||
|
/// making them more resistant to attacks.
|
||||||
|
String generateRandomSalt({int hashLength = 32}) {
|
||||||
|
return generateAsBase64String(hashLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A utility method to generate a ClientID and Client Secret Pair.
|
||||||
|
///
|
||||||
|
/// This function creates an [AuthClient] instance, which can be either public or confidential,
|
||||||
|
/// depending on whether a secret is provided.
|
||||||
|
///
|
||||||
|
/// Any client that allows the authorization code flow must include [redirectURI].
|
||||||
|
///
|
||||||
|
/// Note that [secret] is hashed with a randomly generated salt, and therefore cannot be retrieved
|
||||||
|
/// later. The plain-text secret must be stored securely elsewhere.
|
||||||
|
AuthClient generateAPICredentialPair(
|
||||||
|
String clientID,
|
||||||
|
String? secret, {
|
||||||
|
String? redirectURI,
|
||||||
|
int hashLength = 32,
|
||||||
|
int hashRounds = 1000,
|
||||||
|
Hash? hashFunction,
|
||||||
|
}) {
|
||||||
|
if (secret == null) {
|
||||||
|
return AuthClient.public(clientID, redirectURI: redirectURI);
|
||||||
|
}
|
||||||
|
|
||||||
|
final salt = generateRandomSalt(hashLength: hashLength);
|
||||||
|
final hashed = generatePasswordHash(
|
||||||
|
secret,
|
||||||
|
salt,
|
||||||
|
hashRounds: hashRounds,
|
||||||
|
hashLength: hashLength,
|
||||||
|
hashFunction: hashFunction,
|
||||||
|
);
|
||||||
|
|
||||||
|
return AuthClient.withRedirectURI(clientID, hashed, salt, redirectURI);
|
||||||
|
}
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
import 'package:protevus_auth/auth.dart';
|
|
||||||
import 'package:protevus_hashing/hashing.dart';
|
|
||||||
import 'package:crypto/crypto.dart';
|
|
||||||
|
|
||||||
export 'auth_code_controller.dart';
|
|
||||||
export 'auth_controller.dart';
|
|
||||||
export 'auth_redirect_controller.dart';
|
|
||||||
export 'authorization_parser.dart';
|
|
||||||
export 'authorization_server.dart';
|
|
||||||
export 'authorizer.dart';
|
|
||||||
export 'exceptions.dart';
|
|
||||||
export 'objects.dart';
|
|
||||||
export 'protocols.dart';
|
|
||||||
export 'validator.dart';
|
|
||||||
|
|
||||||
/// A utility method to generate a password hash using the PBKDF2 scheme.
|
|
||||||
///
|
|
||||||
///
|
|
||||||
String generatePasswordHash(
|
|
||||||
String password,
|
|
||||||
String salt, {
|
|
||||||
int hashRounds = 1000,
|
|
||||||
int hashLength = 32,
|
|
||||||
Hash? hashFunction,
|
|
||||||
}) {
|
|
||||||
final generator = PBKDF2(hashAlgorithm: hashFunction ?? sha256);
|
|
||||||
return generator.generateBase64Key(password, salt, hashRounds, hashLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A utility method to generate a random base64 salt.
|
|
||||||
///
|
|
||||||
///
|
|
||||||
String generateRandomSalt({int hashLength = 32}) {
|
|
||||||
return generateAsBase64String(hashLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A utility method to generate a ClientID and Client Secret Pair.
|
|
||||||
///
|
|
||||||
/// [secret] may be null. If secret is null, the return value is a 'public' client. Otherwise, the
|
|
||||||
/// client is 'confidential'. Public clients must not include a client secret when sent to the
|
|
||||||
/// authorization server. Confidential clients must include the secret in all requests. Use public clients when
|
|
||||||
/// the source code of the client application is visible, i.e. a JavaScript browser application.
|
|
||||||
///
|
|
||||||
/// Any client that allows the authorization code flow must include [redirectURI].
|
|
||||||
///
|
|
||||||
/// Note that [secret] is hashed with a randomly generated salt, and therefore cannot be retrieved
|
|
||||||
/// later. The plain-text secret must be stored securely elsewhere.
|
|
||||||
AuthClient generateAPICredentialPair(
|
|
||||||
String clientID,
|
|
||||||
String? secret, {
|
|
||||||
String? redirectURI,
|
|
||||||
int hashLength = 32,
|
|
||||||
int hashRounds = 1000,
|
|
||||||
Hash? hashFunction,
|
|
||||||
}) {
|
|
||||||
if (secret == null) {
|
|
||||||
return AuthClient.public(clientID, redirectURI: redirectURI);
|
|
||||||
}
|
|
||||||
|
|
||||||
final salt = generateRandomSalt(hashLength: hashLength);
|
|
||||||
final hashed = generatePasswordHash(
|
|
||||||
secret,
|
|
||||||
salt,
|
|
||||||
hashRounds: hashRounds,
|
|
||||||
hashLength: hashLength,
|
|
||||||
hashFunction: hashFunction,
|
|
||||||
);
|
|
||||||
|
|
||||||
return AuthClient.withRedirectURI(clientID, hashed, salt, redirectURI);
|
|
||||||
}
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
@ -7,13 +16,20 @@ import 'package:protevus_http/http.dart';
|
||||||
import 'package:protevus_openapi/v3.dart';
|
import 'package:protevus_openapi/v3.dart';
|
||||||
|
|
||||||
/// Provides [AuthCodeController] with application-specific behavior.
|
/// Provides [AuthCodeController] with application-specific behavior.
|
||||||
|
///
|
||||||
|
/// This abstract class defines the interface for a delegate that can be used
|
||||||
|
/// with [AuthCodeController] to customize the rendering of the login form.
|
||||||
|
/// It is deprecated along with [AuthCodeController], and developers are
|
||||||
|
/// advised to see the documentation for alternative approaches.
|
||||||
|
///
|
||||||
|
/// The main responsibility of this delegate is to generate an HTML
|
||||||
|
/// representation of a login form when requested by the [AuthCodeController].
|
||||||
@Deprecated('AuthCodeController is deprecated. See docs.')
|
@Deprecated('AuthCodeController is deprecated. See docs.')
|
||||||
abstract class AuthCodeControllerDelegate {
|
abstract class AuthCodeControllerDelegate {
|
||||||
/// Returns an HTML representation of a login form.
|
/// Returns an HTML representation of a login form.
|
||||||
///
|
///
|
||||||
/// Invoked when [AuthCodeController.getAuthorizationPage] is called in response to a GET request.
|
/// This method is responsible for generating the HTML content of a login form
|
||||||
/// Must provide HTML that will be returned to the browser for rendering. This form submission of this page
|
/// that will be displayed to the user when they attempt to authenticate.
|
||||||
/// should be a POST to [requestUri].
|
|
||||||
///
|
///
|
||||||
/// The form submission should include the values of [responseType], [clientID], [state], [scope]
|
/// The form submission should include the values of [responseType], [clientID], [state], [scope]
|
||||||
/// as well as user-entered username and password in `x-www-form-urlencoded` data, e.g.
|
/// as well as user-entered username and password in `x-www-form-urlencoded` data, e.g.
|
||||||
|
@ -37,24 +53,29 @@ abstract class AuthCodeControllerDelegate {
|
||||||
|
|
||||||
/// Controller for issuing OAuth 2.0 authorization codes.
|
/// Controller for issuing OAuth 2.0 authorization codes.
|
||||||
///
|
///
|
||||||
/// Deprecated, use [AuthRedirectController] instead.
|
/// This controller handles the authorization code grant flow of OAuth 2.0. It provides
|
||||||
|
/// endpoints for both initiating the flow (GET request) and completing it (POST request).
|
||||||
///
|
///
|
||||||
/// This controller provides an endpoint for the creating an OAuth 2.0 authorization code. This authorization code
|
|
||||||
/// can be exchanged for an access token with an [AuthController]. This is known as the OAuth 2.0 'Authorization Code Grant' flow.
|
|
||||||
///
|
|
||||||
/// See operation methods [getAuthorizationPage] and [authorize] for more details.
|
|
||||||
///
|
|
||||||
/// Usage:
|
|
||||||
///
|
|
||||||
/// router
|
|
||||||
/// .route("/auth/code")
|
/// .route("/auth/code")
|
||||||
/// .link(() => new AuthCodeController(authServer));
|
/// .link(() => new AuthCodeController(authServer));
|
||||||
///
|
|
||||||
@Deprecated('Use AuthRedirectController instead.')
|
@Deprecated('Use AuthRedirectController instead.')
|
||||||
class AuthCodeController extends ResourceController {
|
class AuthCodeController extends ResourceController {
|
||||||
/// Creates a new instance of an [AuthCodeController].
|
/// Creates a new instance of an [AuthCodeController].
|
||||||
///
|
///
|
||||||
/// [authServer] is the required authorization server. If [delegate] is provided, this controller will return a login page for all GET requests.
|
/// This constructor initializes an [AuthCodeController] with the provided [authServer].
|
||||||
|
/// It is marked as deprecated, and users are advised to use [AuthRedirectController] instead.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [authServer]: The required authorization server used for handling authentication.
|
||||||
|
/// - [delegate]: An optional [AuthCodeControllerDelegate] that, if provided, allows this controller
|
||||||
|
/// to return a login page for all GET requests.
|
||||||
|
///
|
||||||
|
/// The constructor also sets the [acceptedContentTypes] to only accept
|
||||||
|
/// "application/x-www-form-urlencoded" content type.
|
||||||
|
///
|
||||||
|
/// This controller is part of the OAuth 2.0 authorization code flow and is used
|
||||||
|
/// for issuing authorization codes. However, due to its deprecated status,
|
||||||
|
/// it's recommended to transition to newer alternatives as specified in the documentation.
|
||||||
@Deprecated('Use AuthRedirectController instead.')
|
@Deprecated('Use AuthRedirectController instead.')
|
||||||
AuthCodeController(this.authServer, {this.delegate}) {
|
AuthCodeController(this.authServer, {this.delegate}) {
|
||||||
acceptedContentTypes = [
|
acceptedContentTypes = [
|
||||||
|
@ -63,6 +84,11 @@ class AuthCodeController extends ResourceController {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A reference to the [AuthServer] used to grant authorization codes.
|
/// A reference to the [AuthServer] used to grant authorization codes.
|
||||||
|
///
|
||||||
|
/// This [AuthServer] instance is responsible for handling the authentication
|
||||||
|
/// and authorization processes, including the generation and validation of
|
||||||
|
/// authorization codes. It is a crucial component of the OAuth 2.0 flow
|
||||||
|
/// implemented by this controller.
|
||||||
final AuthServer authServer;
|
final AuthServer authServer;
|
||||||
|
|
||||||
/// A randomly generated value the client can use to verify the origin of the redirect.
|
/// A randomly generated value the client can use to verify the origin of the redirect.
|
||||||
|
@ -70,23 +96,53 @@ class AuthCodeController extends ResourceController {
|
||||||
/// Clients must include this query parameter and verify that any redirects from this
|
/// Clients must include this query parameter and verify that any redirects from this
|
||||||
/// server have the same value for 'state' as passed in. This value is usually a randomly generated
|
/// server have the same value for 'state' as passed in. This value is usually a randomly generated
|
||||||
/// session identifier.
|
/// session identifier.
|
||||||
|
///
|
||||||
|
/// This property is bound to the 'state' query parameter in the request URL.
|
||||||
|
/// It plays a crucial role in preventing cross-site request forgery (CSRF) attacks
|
||||||
|
/// by ensuring that the authorization request and response originate from the same client session.
|
||||||
|
///
|
||||||
|
/// The 'state' parameter should be:
|
||||||
|
/// - Unique for each authorization request
|
||||||
|
/// - Securely generated to be unguessable
|
||||||
|
/// - Stored by the client for later comparison
|
||||||
|
///
|
||||||
|
/// When the authorization server redirects the user back to the client,
|
||||||
|
/// it includes this state value, allowing the client to verify that the redirect
|
||||||
|
/// is in response to its own authorization request.
|
||||||
@Bind.query("state")
|
@Bind.query("state")
|
||||||
String? state;
|
String? state;
|
||||||
|
|
||||||
|
/// The type of response expected from the authorization server.
|
||||||
|
///
|
||||||
|
/// This parameter is bound to the 'response_type' query parameter in the request URL.
|
||||||
|
/// For the authorization code flow, this value must be 'code'.
|
||||||
|
///
|
||||||
|
/// The response type indicates to the authorization server which grant type
|
||||||
|
/// is being utilized. In this case, 'code' signifies that the client expects
|
||||||
|
/// to receive an authorization code that can be exchanged for an access token
|
||||||
|
/// in a subsequent request.
|
||||||
|
///
|
||||||
/// Must be 'code'.
|
/// Must be 'code'.
|
||||||
@Bind.query("response_type")
|
@Bind.query("response_type")
|
||||||
String? responseType;
|
String? responseType;
|
||||||
|
|
||||||
/// The client ID of the authenticating client.
|
/// The client ID of the authenticating client.
|
||||||
///
|
///
|
||||||
/// This must be a valid client ID according to [authServer].\
|
/// This property is bound to the 'client_id' query parameter in the request URL.
|
||||||
|
/// It represents the unique identifier of the client application requesting authorization.
|
||||||
|
///
|
||||||
|
/// The client ID must be registered and valid according to the [authServer].
|
||||||
|
/// It is used to identify the client during the OAuth 2.0 authorization process.
|
||||||
|
///
|
||||||
|
/// This field is nullable, but typically required for most OAuth 2.0 flows.
|
||||||
|
/// If not provided or invalid, the authorization request may be rejected.
|
||||||
@Bind.query("client_id")
|
@Bind.query("client_id")
|
||||||
String? clientID;
|
String? clientID;
|
||||||
|
|
||||||
/// Renders an HTML login form.
|
/// Renders an HTML login form.
|
||||||
final AuthCodeControllerDelegate? delegate;
|
final AuthCodeControllerDelegate? delegate;
|
||||||
|
|
||||||
/// Returns an HTML login form.
|
/// Returns an HTML login form for OAuth 2.0 authorization.
|
||||||
///
|
///
|
||||||
/// A client that wishes to authenticate with this server should direct the user
|
/// A client that wishes to authenticate with this server should direct the user
|
||||||
/// to this page. The user will enter their username and password that is sent as a POST
|
/// to this page. The user will enter their username and password that is sent as a POST
|
||||||
|
@ -115,20 +171,61 @@ class AuthCodeController extends ResourceController {
|
||||||
|
|
||||||
/// Creates a one-time use authorization code.
|
/// Creates a one-time use authorization code.
|
||||||
///
|
///
|
||||||
/// This method will respond with a redirect that contains an authorization code ('code')
|
/// This method handles the POST request for the OAuth 2.0 authorization code grant flow.
|
||||||
/// and the passed in 'state'. If this request fails, the redirect URL
|
/// It authenticates the user with the provided credentials and, if successful, generates
|
||||||
/// will contain an 'error' key instead of the authorization code.
|
/// a one-time use authorization code.
|
||||||
///
|
///
|
||||||
/// This method is typically invoked by the login form returned from the GET to this controller.
|
/// This method is typically invoked by the login form returned from the GET to this controller.
|
||||||
@Operation.post()
|
@Operation.post()
|
||||||
Future<Response> authorize({
|
Future<Response> authorize({
|
||||||
/// The username of the authenticating user.
|
/// The username of the authenticating user.
|
||||||
|
///
|
||||||
|
/// This parameter is bound to the 'username' query parameter in the request URL.
|
||||||
|
/// It represents the username of the user attempting to authenticate.
|
||||||
|
///
|
||||||
|
/// The username is used in conjunction with the password to verify the user's identity
|
||||||
|
/// during the OAuth 2.0 authorization code grant flow. It is a crucial part of the
|
||||||
|
/// user authentication process.
|
||||||
|
///
|
||||||
|
/// This field is nullable, but typically required for successful authentication.
|
||||||
|
/// If not provided or invalid, the authorization request may be rejected.
|
||||||
@Bind.query("username") String? username,
|
@Bind.query("username") String? username,
|
||||||
|
|
||||||
/// The password of the authenticating user.
|
/// The password of the authenticating user.
|
||||||
|
///
|
||||||
|
/// This parameter is bound to the 'password' query parameter in the request URL.
|
||||||
|
/// It represents the password of the user attempting to authenticate.
|
||||||
|
///
|
||||||
|
/// The password is used in conjunction with the username to verify the user's identity
|
||||||
|
/// during the OAuth 2.0 authorization code grant flow. It is a crucial part of the
|
||||||
|
/// user authentication process.
|
||||||
|
///
|
||||||
|
/// This field is nullable, but typically required for successful authentication.
|
||||||
|
/// If not provided or invalid, the authorization request may be rejected.
|
||||||
|
///
|
||||||
|
/// Note: Transmitting passwords as query parameters is not recommended for production
|
||||||
|
/// environments due to security concerns. This approach should only be used in
|
||||||
|
/// controlled, secure environments or for testing purposes.
|
||||||
@Bind.query("password") String? password,
|
@Bind.query("password") String? password,
|
||||||
|
|
||||||
/// A space-delimited list of access scopes being requested.
|
/// A space-delimited list of access scopes being requested.
|
||||||
|
///
|
||||||
|
/// This parameter is bound to the 'scope' query parameter in the request URL.
|
||||||
|
/// It represents the permissions that the client is requesting access to.
|
||||||
|
///
|
||||||
|
/// The scope is typically a string containing one or more space-separated
|
||||||
|
/// scope values. Each scope value represents a specific permission or
|
||||||
|
/// set of permissions that the client is requesting.
|
||||||
|
///
|
||||||
|
/// For example, a scope might look like: "read_profile edit_profile"
|
||||||
|
///
|
||||||
|
/// The authorization server can use this information to present
|
||||||
|
/// the user with a consent screen, allowing them to approve or deny
|
||||||
|
/// specific permissions requested by the client.
|
||||||
|
///
|
||||||
|
/// This field is optional. If not provided, the authorization server
|
||||||
|
/// may assign a default set of scopes or handle the request according
|
||||||
|
/// to its own policies.
|
||||||
@Bind.query("scope") String? scope,
|
@Bind.query("scope") String? scope,
|
||||||
}) async {
|
}) async {
|
||||||
final client = await authServer.getClient(clientID!);
|
final client = await authServer.getClient(clientID!);
|
||||||
|
@ -174,6 +271,25 @@ class AuthCodeController extends ResourceController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Overrides the default documentation for the request body of this controller's operations.
|
||||||
|
///
|
||||||
|
/// This method is called during the OpenAPI documentation generation process.
|
||||||
|
/// It modifies the request body schema for POST operations to:
|
||||||
|
/// 1. Set the format of the 'password' field to "password".
|
||||||
|
/// 2. Mark certain fields as required in the request body.
|
||||||
|
///
|
||||||
|
/// The method specifically targets the "application/x-www-form-urlencoded" content type
|
||||||
|
/// in POST requests. It updates the schema to indicate that the 'password' field should
|
||||||
|
/// be treated as a password input, and sets the following fields as required:
|
||||||
|
/// - client_id
|
||||||
|
/// - state
|
||||||
|
/// - response_type
|
||||||
|
/// - username
|
||||||
|
/// - password
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// An [APIRequestBody] object representing the documented request body,
|
||||||
|
/// or null if there is no request body for the operation.
|
||||||
@override
|
@override
|
||||||
APIRequestBody? documentOperationRequestBody(
|
APIRequestBody? documentOperationRequestBody(
|
||||||
APIDocumentContext context,
|
APIDocumentContext context,
|
||||||
|
@ -194,6 +310,20 @@ class AuthCodeController extends ResourceController {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Overrides the default documentation for operation parameters.
|
||||||
|
///
|
||||||
|
/// This method is called during the OpenAPI documentation generation process.
|
||||||
|
/// It modifies the parameter documentation for the controller's operations by:
|
||||||
|
/// 1. Retrieving the default parameters using the superclass method.
|
||||||
|
/// 2. Setting all parameters except 'scope' as required.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// context: The [APIDocumentContext] for the current documentation generation.
|
||||||
|
/// operation: The [Operation] being documented.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A List of [APIParameter] objects representing the documented parameters,
|
||||||
|
/// with updated 'isRequired' properties.
|
||||||
@override
|
@override
|
||||||
List<APIParameter> documentOperationParameters(
|
List<APIParameter> documentOperationParameters(
|
||||||
APIDocumentContext context,
|
APIDocumentContext context,
|
||||||
|
@ -206,6 +336,28 @@ class AuthCodeController extends ResourceController {
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates documentation for the operation responses of this controller.
|
||||||
|
///
|
||||||
|
/// This method overrides the default behavior to provide custom documentation
|
||||||
|
/// for the GET and POST operations of the AuthCodeController.
|
||||||
|
///
|
||||||
|
/// For GET requests:
|
||||||
|
/// - Defines a 200 OK response that serves a login form in HTML format.
|
||||||
|
///
|
||||||
|
/// For POST requests:
|
||||||
|
/// - Defines a 302 Found (Moved Temporarily) response for successful requests,
|
||||||
|
/// indicating that the 'code' query parameter in the redirect URI contains
|
||||||
|
/// the authorization code, or an 'error' parameter is present for errors.
|
||||||
|
/// - Defines a 400 Bad Request response for cases where the 'client_id' is
|
||||||
|
/// invalid and the redirect URI cannot be verified.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// context: The API documentation context.
|
||||||
|
/// operation: The operation being documented.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A Map of status codes to APIResponse objects describing the possible
|
||||||
|
/// responses for the operation.
|
||||||
@override
|
@override
|
||||||
Map<String, APIResponse> documentOperationResponses(
|
Map<String, APIResponse> documentOperationResponses(
|
||||||
APIDocumentContext context,
|
APIDocumentContext context,
|
||||||
|
@ -240,6 +392,24 @@ class AuthCodeController extends ResourceController {
|
||||||
throw StateError("AuthCodeController documentation failed.");
|
throw StateError("AuthCodeController documentation failed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Customizes the documentation for the operations of this controller.
|
||||||
|
///
|
||||||
|
/// This method overrides the default implementation to add additional
|
||||||
|
/// information specific to the OAuth 2.0 authorization code flow.
|
||||||
|
///
|
||||||
|
/// It performs the following tasks:
|
||||||
|
/// 1. Calls the superclass method to get the default operation documentation.
|
||||||
|
/// 2. Updates the authorization URL in the documented authorization code flow
|
||||||
|
/// of the auth server to match the current route.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// context: The [APIDocumentContext] for the current documentation generation.
|
||||||
|
/// route: The route string for this controller.
|
||||||
|
/// path: The [APIPath] object representing the path in the API documentation.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A Map of operation names to [APIOperation] objects representing the
|
||||||
|
/// documented operations for this controller.
|
||||||
@override
|
@override
|
||||||
Map<String, APIOperation> documentOperations(
|
Map<String, APIOperation> documentOperations(
|
||||||
APIDocumentContext context,
|
APIDocumentContext context,
|
||||||
|
@ -252,6 +422,27 @@ class AuthCodeController extends ResourceController {
|
||||||
return ops;
|
return ops;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates a redirect response for the OAuth 2.0 authorization code flow.
|
||||||
|
///
|
||||||
|
/// This method constructs a redirect URI based on the provided parameters and
|
||||||
|
/// returns a Response object with appropriate headers for redirection.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [inputUri]: The base URI to redirect to. If null, falls back to the client's redirectURI.
|
||||||
|
/// - [clientStateOrNull]: The state parameter provided by the client. If not null, it's included in the redirect URI.
|
||||||
|
/// - [code]: The authorization code to be included in the redirect URI. Optional.
|
||||||
|
/// - [error]: An AuthServerException containing error details. Optional.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// - A Response object with status 302 (Found) and appropriate headers for redirection.
|
||||||
|
/// - If no valid redirect URI can be constructed, returns a 400 (Bad Request) response.
|
||||||
|
///
|
||||||
|
/// The method constructs the redirect URI by:
|
||||||
|
/// 1. Determining the base URI (from input or client's redirect URI)
|
||||||
|
/// 2. Adding query parameters for code, state, and error as applicable
|
||||||
|
/// 3. Constructing a new URI with these parameters
|
||||||
|
///
|
||||||
|
/// The response includes headers for location, cache control, and pragma.
|
||||||
static Response _redirectResponse(
|
static Response _redirectResponse(
|
||||||
String? inputUri,
|
String? inputUri,
|
||||||
String? clientStateOrNull, {
|
String? clientStateOrNull, {
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
@ -49,6 +58,30 @@ class AuthController extends ResourceController {
|
||||||
|
|
||||||
final AuthorizationBasicParser _parser = const AuthorizationBasicParser();
|
final AuthorizationBasicParser _parser = const AuthorizationBasicParser();
|
||||||
|
|
||||||
|
/// This class, AuthController, is responsible for handling OAuth 2.0 token operations.
|
||||||
|
/// It provides functionality for issuing and refreshing access tokens using various grant types.
|
||||||
|
///
|
||||||
|
/// Key features:
|
||||||
|
/// - Supports 'password', 'refresh_token', and 'authorization_code' grant types
|
||||||
|
/// - Handles client authentication via Basic Authorization header
|
||||||
|
/// - Processes token requests and returns RFC6749 compliant responses
|
||||||
|
/// - Includes error handling for various authentication scenarios
|
||||||
|
/// - Provides OpenAPI documentation support
|
||||||
|
///
|
||||||
|
/// The main method, 'grant', processes token requests based on the provided grant type.
|
||||||
|
/// It interacts with an AuthServer to perform the actual authentication and token generation.
|
||||||
|
///
|
||||||
|
/// This controller also includes methods for generating API documentation,
|
||||||
|
/// including operation parameters, request body, and responses.
|
||||||
|
///
|
||||||
|
/// Usage:
|
||||||
|
/// router
|
||||||
|
/// .route("/auth/token")
|
||||||
|
/// .link(() => new AuthController(authServer));
|
||||||
|
///
|
||||||
|
/// Note: This controller expects client credentials to be provided in the Authorization header
|
||||||
|
/// using the Basic authentication scheme.
|
||||||
|
///
|
||||||
/// Creates or refreshes an authentication token.
|
/// Creates or refreshes an authentication token.
|
||||||
///
|
///
|
||||||
/// When grant_type is 'password', there must be username and password values.
|
/// When grant_type is 'password', there must be username and password values.
|
||||||
|
@ -59,11 +92,88 @@ class AuthController extends ResourceController {
|
||||||
/// include a valid Client ID and Secret in the Basic authorization scheme format.
|
/// include a valid Client ID and Secret in the Basic authorization scheme format.
|
||||||
@Operation.post()
|
@Operation.post()
|
||||||
Future<Response> grant({
|
Future<Response> grant({
|
||||||
|
/// The username of the user attempting to authenticate.
|
||||||
|
///
|
||||||
|
/// This parameter is typically used with the 'password' grant type.
|
||||||
|
/// It should be provided as a query parameter in the request.
|
||||||
@Bind.query("username") String? username,
|
@Bind.query("username") String? username,
|
||||||
|
|
||||||
|
/// The password of the user attempting to authenticate.
|
||||||
|
///
|
||||||
|
/// This parameter is typically used with the 'password' grant type.
|
||||||
|
/// It should be provided as a query parameter in the request.
|
||||||
|
/// Note: Sending passwords as query parameters is not recommended for production environments due to security concerns.
|
||||||
@Bind.query("password") String? password,
|
@Bind.query("password") String? password,
|
||||||
|
|
||||||
|
/// The refresh token used to obtain a new access token.
|
||||||
|
///
|
||||||
|
/// This parameter is typically used with the 'refresh_token' grant type.
|
||||||
|
/// It should be provided as a query parameter in the request.
|
||||||
|
/// The refresh token is used to request a new access token when the current one has expired.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// curl -X POST -d "grant_type=refresh_token&refresh_token=<refresh_token>" https://example.com/auth/token
|
||||||
|
///
|
||||||
|
/// Note: The refresh token should be securely stored and managed by the client application.
|
||||||
|
/// It is important to handle refresh tokens with care to prevent unauthorized access to user resources.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
/// - [RFC 6749, Section 6](https://tools.ietf.org/html/rfc6749#section-6) for more details on the refresh token grant type.
|
||||||
|
/// - [OAuth 2.0 Refresh Token Grant](https://oauth.net/2/grant-types/refresh-token/) for a detailed explanation of the refresh token grant type.
|
||||||
|
/// - [OAuth 2.0 Security Best Current Practice](https://tools.ietf.org/html/draft-ietf-oauth-security-topics-13#section-2.1) for security considerations when implementing OAuth 2.0.
|
||||||
@Bind.query("refresh_token") String? refreshToken,
|
@Bind.query("refresh_token") String? refreshToken,
|
||||||
|
|
||||||
|
/// The authorization code obtained from the authorization server.
|
||||||
|
///
|
||||||
|
/// This parameter is typically used with the 'authorization_code' grant type.
|
||||||
|
/// It should be provided as a query parameter in the request.
|
||||||
|
/// The authorization code is used to request an access token after the user has granted permission to the client application.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// curl -X POST -d "grant_type=authorization_code&code=<authorization_code>&redirect_uri=<redirect_uri>" https://example.com/auth/token
|
||||||
|
///
|
||||||
|
/// Note: The authorization code should be securely transmitted and used only once to prevent replay attacks.
|
||||||
|
/// It is important to handle authorization codes with care to protect user data and ensure the security of the OAuth 2.0 flow.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
/// - [RFC 6749, Section 4.1.3](https://tools.ietf.org/html/rfc6749#section-4.1.3) for more details on the authorization code grant type.
|
||||||
|
/// - [OAuth 2.0 Authorization Code Grant](https://oauth.net/2/grant-types/authorization-code/) for a detailed explanation of the authorization code grant type.
|
||||||
|
/// - [OAuth 2.0 Security Best Current Practice](https://tools.ietf.org/html/draft-ietf-oauth-security-topics-13#section-2.1) for security considerations when implementing OAuth 2.0.
|
||||||
@Bind.query("code") String? authCode,
|
@Bind.query("code") String? authCode,
|
||||||
|
|
||||||
|
/// The URI to which the authorization server will redirect the user-agent after obtaining authorization.
|
||||||
|
///
|
||||||
|
/// This parameter is typically used with the 'authorization_code' grant type.
|
||||||
|
/// It should be provided as a query parameter in the request.
|
||||||
|
/// The redirect URI is used to ensure that the authorization code is sent to the correct client application.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// curl -X POST -d "grant_type=authorization_code&code=<authorization_code>&redirect_uri=https://example.com/callback" https://example.com/auth/token
|
||||||
|
///
|
||||||
|
/// Note: The redirect URI should be registered with the authorization server and should match the URI used during the authorization request.
|
||||||
|
/// It is important to handle redirect URIs with care to prevent unauthorized access to user resources.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
/// - [RFC 6749, Section 4.1.3](https://tools.ietf.org/html/rfc6749#section-4.1.3) for more details on the authorization code grant type.
|
||||||
|
/// - [OAuth 2.0 Authorization Code Grant](https://oauth.net/2/grant-types/authorization-code/) for a detailed explanation of the authorization code grant type.
|
||||||
|
/// - [OAuth 2.0 Security Best Current Practice](https://tools.ietf.org/html/draft-ietf-oauth-security-topics-13#section-2.1) for security considerations when implementing OAuth 2.0.
|
||||||
@Bind.query("grant_type") String? grantType,
|
@Bind.query("grant_type") String? grantType,
|
||||||
|
|
||||||
|
/// The scope of the access request, which defines the resources and permissions that the client application is requesting.
|
||||||
|
///
|
||||||
|
/// This parameter is optional and should be provided as a query parameter in the request.
|
||||||
|
/// The scope value is a space-delimited list of scope identifiers, which indicate the specific resources and permissions that the client application needs to access.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// curl -X POST -d "grant_type=authorization_code&code=<authorization_code>&redirect_uri=<redirect_uri>&scope=read write" https://example.com/auth/token
|
||||||
|
///
|
||||||
|
/// Note: The scope parameter should be used to limit the access granted to the client application to only the necessary resources and permissions.
|
||||||
|
/// It is important to handle scope values with care to ensure that the client application does not have unintended access to user resources.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
/// - [RFC 6749, Section 3.3](https://tools.ietf.org/html/rfc6749#section-3.3) for more details on the scope parameter.
|
||||||
|
/// - [OAuth 2.0 Security Best Current Practice](https://tools.ietf.org/html/draft-ietf-oauth-security-topics-13#section-2.2) for security considerations when implementing OAuth 2.0 scope.
|
||||||
|
/// - [OAuth 2.0 Scope](https://oauth.net/2/scope/) for a detailed explanation of the scope parameter and its usage.
|
||||||
@Bind.query("scope") String? scope,
|
@Bind.query("scope") String? scope,
|
||||||
}) async {
|
}) async {
|
||||||
AuthBasicCredentials basicRecord;
|
AuthBasicCredentials basicRecord;
|
||||||
|
@ -118,6 +228,28 @@ class AuthController extends ResourceController {
|
||||||
|
|
||||||
/// Transforms a [AuthToken] into a [Response] object with an RFC6749 compliant JSON token
|
/// Transforms a [AuthToken] into a [Response] object with an RFC6749 compliant JSON token
|
||||||
/// as the HTTP response body.
|
/// as the HTTP response body.
|
||||||
|
///
|
||||||
|
/// This static method takes an [AuthToken] object and converts it into a [Response] object
|
||||||
|
/// that adheres to the OAuth 2.0 specification (RFC6749). The response includes:
|
||||||
|
/// - A status code of 200 (OK)
|
||||||
|
/// - Headers to prevent caching of the token
|
||||||
|
/// - A body containing the token information in JSON format
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - token: An [AuthToken] object containing the authentication token details
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A [Response] object with the token information, ready to be sent to the client
|
||||||
|
///
|
||||||
|
/// Example usage:
|
||||||
|
/// ```dart
|
||||||
|
/// AuthToken myToken = // ... obtain token
|
||||||
|
/// Response response = AuthController.tokenResponse(myToken);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
/// - [RFC6749](https://tools.ietf.org/html/rfc6749) for the OAuth 2.0 specification
|
||||||
|
/// - [AuthToken] for the structure of the token object
|
||||||
static Response tokenResponse(AuthToken token) {
|
static Response tokenResponse(AuthToken token) {
|
||||||
return Response(
|
return Response(
|
||||||
HttpStatus.ok,
|
HttpStatus.ok,
|
||||||
|
@ -126,6 +258,24 @@ class AuthController extends ResourceController {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Processes the response before it is sent, specifically handling duplicate parameter errors.
|
||||||
|
///
|
||||||
|
/// This method is called just before a response is sent. It checks for responses with a 400 status code
|
||||||
|
/// and modifies the error message in case of duplicate parameters in the request, which violates the OAuth 2.0 specification.
|
||||||
|
///
|
||||||
|
/// The method performs the following actions:
|
||||||
|
/// 1. Checks if the response status code is 400 (Bad Request).
|
||||||
|
/// 2. If the response body contains an "error" key with a string value, it examines the error message.
|
||||||
|
/// 3. If the error message indicates multiple values (likely due to duplicate parameters), it replaces the error message
|
||||||
|
/// with a standard "invalid_request" error as defined in the OAuth 2.0 specification.
|
||||||
|
///
|
||||||
|
/// This post-processing helps to maintain compliance with the OAuth 2.0 specification by providing a standard error
|
||||||
|
/// response for invalid requests, even in the case of duplicate parameters which are not explicitly handled elsewhere.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// response: The Response object that will be sent to the client.
|
||||||
|
///
|
||||||
|
/// Note: This method directly modifies the response object if conditions are met.
|
||||||
@override
|
@override
|
||||||
void willSendResponse(Response response) {
|
void willSendResponse(Response response) {
|
||||||
if (response.statusCode == 400) {
|
if (response.statusCode == 400) {
|
||||||
|
@ -145,6 +295,20 @@ class AuthController extends ResourceController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Modifies the list of API parameters documented for this operation.
|
||||||
|
///
|
||||||
|
/// This method overrides the default behavior to remove the 'Authorization' header
|
||||||
|
/// from the list of documented parameters. This is typically done because the
|
||||||
|
/// Authorization header is handled separately in OAuth 2.0 flows and doesn't need
|
||||||
|
/// to be explicitly documented as an operation parameter.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - context: The current API documentation context.
|
||||||
|
/// - operation: The operation being documented (can be null).
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A list of [APIParameter] objects representing the documented parameters
|
||||||
|
/// for this operation, with the Authorization header removed.
|
||||||
@override
|
@override
|
||||||
List<APIParameter> documentOperationParameters(
|
List<APIParameter> documentOperationParameters(
|
||||||
APIDocumentContext context,
|
APIDocumentContext context,
|
||||||
|
@ -155,6 +319,21 @@ class AuthController extends ResourceController {
|
||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Customizes the documentation for the request body of this operation.
|
||||||
|
///
|
||||||
|
/// This method overrides the default behavior to add specific requirements
|
||||||
|
/// and formatting for the OAuth 2.0 token endpoint:
|
||||||
|
///
|
||||||
|
/// 1. It marks the 'grant_type' parameter as required in the request body.
|
||||||
|
/// 2. It sets the format of the 'password' field to "password", indicating
|
||||||
|
/// that it should be treated as a sensitive input in API documentation tools.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - context: The current API documentation context.
|
||||||
|
/// - operation: The operation being documented (can be null).
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// An [APIRequestBody] object with the customized schema for the request body.
|
||||||
@override
|
@override
|
||||||
APIRequestBody documentOperationRequestBody(
|
APIRequestBody documentOperationRequestBody(
|
||||||
APIDocumentContext context,
|
APIDocumentContext context,
|
||||||
|
@ -169,6 +348,20 @@ class AuthController extends ResourceController {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Customizes the API documentation for the operations handled by this controller.
|
||||||
|
///
|
||||||
|
/// This method overrides the default behavior to:
|
||||||
|
/// 1. Add OAuth 2.0 client authentication security requirement to all operations.
|
||||||
|
/// 2. Set the token and refresh URLs for the documented authorization code flow.
|
||||||
|
/// 3. Set the token and refresh URLs for the documented password flow.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - context: The current API documentation context.
|
||||||
|
/// - route: The route string for the current operations.
|
||||||
|
/// - path: The APIPath object representing the current path.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A map of operation names to APIOperation objects with the customized documentation.
|
||||||
@override
|
@override
|
||||||
Map<String, APIOperation> documentOperations(
|
Map<String, APIOperation> documentOperations(
|
||||||
APIDocumentContext context,
|
APIDocumentContext context,
|
||||||
|
@ -193,6 +386,25 @@ class AuthController extends ResourceController {
|
||||||
return operations;
|
return operations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Defines the API responses for the token operation in OpenAPI documentation.
|
||||||
|
///
|
||||||
|
/// This method overrides the default behavior to provide custom documentation
|
||||||
|
/// for the responses of the token endpoint. It describes two possible responses:
|
||||||
|
///
|
||||||
|
/// 1. A successful response (200 OK) when credentials are successfully exchanged for a token.
|
||||||
|
/// This response includes details about the issued token such as access_token, token_type,
|
||||||
|
/// expiration time, refresh_token, and scope.
|
||||||
|
///
|
||||||
|
/// 2. An error response (400 Bad Request) for cases of invalid credentials or missing parameters.
|
||||||
|
/// This response includes an error message.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - context: The current API documentation context.
|
||||||
|
/// - operation: The operation being documented (can be null).
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A map of status codes to APIResponse objects describing the possible
|
||||||
|
/// responses for this operation.
|
||||||
@override
|
@override
|
||||||
Map<String, APIResponse> documentOperationResponses(
|
Map<String, APIResponse> documentOperationResponses(
|
||||||
APIDocumentContext context,
|
APIDocumentContext context,
|
||||||
|
@ -218,6 +430,27 @@ class AuthController extends ResourceController {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a Response object for an authentication error.
|
||||||
|
///
|
||||||
|
/// This method generates a standardized HTTP response for various authentication
|
||||||
|
/// errors that may occur during the OAuth 2.0 flow. It uses the [AuthRequestError]
|
||||||
|
/// enum to determine the specific error and creates a response with:
|
||||||
|
/// - A status code of 400 (Bad Request)
|
||||||
|
/// - A JSON body containing an "error" key with a description of the error
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - error: An [AuthRequestError] enum representing the specific authentication error.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A [Response] object with status code 400 and a JSON body describing the error.
|
||||||
|
///
|
||||||
|
/// Example usage:
|
||||||
|
/// ```dart
|
||||||
|
/// Response errorResponse = _responseForError(AuthRequestError.invalidRequest);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The error string in the response body is generated using [AuthServerException.errorString],
|
||||||
|
/// ensuring consistency with OAuth 2.0 error reporting standards.
|
||||||
Response _responseForError(AuthRequestError error) {
|
Response _responseForError(AuthRequestError error) {
|
||||||
return Response.badRequest(
|
return Response.badRequest(
|
||||||
body: {"error": AuthServerException.errorString(error)},
|
body: {"error": AuthServerException.errorString(error)},
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
@ -6,13 +15,31 @@ import 'package:protevus_auth/auth.dart';
|
||||||
import 'package:protevus_http/http.dart';
|
import 'package:protevus_http/http.dart';
|
||||||
import 'package:protevus_openapi/v3.dart';
|
import 'package:protevus_openapi/v3.dart';
|
||||||
|
|
||||||
/// Provides [AuthRedirectController] with application-specific behavior.
|
/// Abstract class defining the interface for providing application-specific behavior to [AuthRedirectController].
|
||||||
|
///
|
||||||
|
/// This delegate is responsible for rendering the HTML login form when [AuthRedirectController.getAuthorizationPage]
|
||||||
|
/// is called in response to a GET request. Implementations of this class should customize the login form
|
||||||
|
/// according to the application's needs while ensuring that the form submission adheres to the required format.
|
||||||
|
///
|
||||||
|
/// The rendered form should:
|
||||||
|
/// - Be submitted as a POST request to the [requestUri].
|
||||||
|
/// - Include all provided parameters (responseType, clientID, state, scope) in the form submission.
|
||||||
|
/// - Collect and include user-entered username and password.
|
||||||
|
/// - Use 'application/x-www-form-urlencoded' as the Content-Type for form submission.
|
||||||
|
///
|
||||||
|
/// Example of expected form submission:
|
||||||
|
///
|
||||||
|
/// POST https://example.com/auth/code
|
||||||
|
/// Content-Type: application/x-www-form-urlencoded
|
||||||
|
///
|
||||||
|
/// response_type=code&client_id=com.conduit.app&state=o9u3jla&username=bob&password=password
|
||||||
|
///
|
||||||
|
/// Implementations should take care to handle all provided parameters and ensure secure transmission of credentials.
|
||||||
abstract class AuthRedirectControllerDelegate {
|
abstract class AuthRedirectControllerDelegate {
|
||||||
/// Returns an HTML representation of a login form.
|
/// Returns an HTML representation of a login form.
|
||||||
///
|
///
|
||||||
/// Invoked when [AuthRedirectController.getAuthorizationPage] is called in response to a GET request.
|
/// This method is responsible for generating and returning the HTML content for a login form
|
||||||
/// Must provide HTML that will be returned to the browser for rendering. This form submission of this page
|
/// when [AuthRedirectController.getAuthorizationPage] is called in response to a GET request.
|
||||||
/// should be a POST to [requestUri].
|
|
||||||
///
|
///
|
||||||
/// The form submission should include the values of [responseType], [clientID], [state], [scope]
|
/// The form submission should include the values of [responseType], [clientID], [state], [scope]
|
||||||
/// as well as user-entered username and password in `x-www-form-urlencoded` data, e.g.
|
/// as well as user-entered username and password in `x-www-form-urlencoded` data, e.g.
|
||||||
|
@ -51,7 +78,23 @@ abstract class AuthRedirectControllerDelegate {
|
||||||
class AuthRedirectController extends ResourceController {
|
class AuthRedirectController extends ResourceController {
|
||||||
/// Creates a new instance of an [AuthRedirectController].
|
/// Creates a new instance of an [AuthRedirectController].
|
||||||
///
|
///
|
||||||
/// [authServer] is the required authorization server. If [delegate] is provided, this controller will return a login page for all GET requests.
|
/// This constructor initializes an [AuthRedirectController] with the provided [authServer].
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [authServer]: The required authorization server.
|
||||||
|
/// - [delegate]: Optional. If provided, this controller will return a login page for all GET requests.
|
||||||
|
/// - [allowsImplicit]: Optional. Defaults to true. Determines if the controller allows the Implicit Grant Flow.
|
||||||
|
///
|
||||||
|
/// The constructor also sets the [acceptedContentTypes] to ["application/x-www-form-urlencoded"].
|
||||||
|
///
|
||||||
|
/// Usage:
|
||||||
|
/// ```dart
|
||||||
|
/// final authRedirectController = AuthRedirectController(
|
||||||
|
/// myAuthServer,
|
||||||
|
/// delegate: myDelegate,
|
||||||
|
/// allowsImplicit: false,
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
AuthRedirectController(
|
AuthRedirectController(
|
||||||
this.authServer, {
|
this.authServer, {
|
||||||
this.delegate,
|
this.delegate,
|
||||||
|
@ -62,38 +105,108 @@ class AuthRedirectController extends ResourceController {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A pre-defined Response object for unsupported response types.
|
||||||
|
///
|
||||||
|
/// This static final variable creates a Response object with:
|
||||||
|
/// - HTTP status code 400 (Bad Request)
|
||||||
|
/// - HTML content indicating an "unsupported_response_type" error
|
||||||
|
/// - Content-Type set to text/html
|
||||||
|
///
|
||||||
|
/// This response is used when the 'response_type' parameter in the request
|
||||||
|
/// is neither 'code' nor 'token', or when 'token' is requested but implicit
|
||||||
|
/// grant flow is not allowed.
|
||||||
static final Response _unsupportedResponseTypeResponse = Response.badRequest(
|
static final Response _unsupportedResponseTypeResponse = Response.badRequest(
|
||||||
body: "<h1>Error</h1><p>unsupported_response_type</p>",
|
body: "<h1>Error</h1><p>unsupported_response_type</p>",
|
||||||
)..contentType = ContentType.html;
|
)..contentType = ContentType.html;
|
||||||
|
|
||||||
/// A reference to the [AuthServer] used to grant authorization codes and access tokens.
|
/// A reference to the [AuthServer] used to grant authorization codes and access tokens.
|
||||||
|
///
|
||||||
|
/// This property holds an instance of [AuthServer] which is responsible for
|
||||||
|
/// handling the authentication and authorization processes. It is used by
|
||||||
|
/// this controller to issue authorization codes and access tokens as part of
|
||||||
|
/// the OAuth 2.0 flow.
|
||||||
|
///
|
||||||
|
/// The [AuthServer] instance should be properly configured and initialized
|
||||||
|
/// before being assigned to this property.
|
||||||
late final AuthServer authServer;
|
late final AuthServer authServer;
|
||||||
|
|
||||||
/// When true, the controller allows for the Implicit Grant Flow
|
/// Determines whether the controller allows the OAuth 2.0 Implicit Grant Flow.
|
||||||
|
///
|
||||||
|
/// When set to true, the controller will process requests for access tokens
|
||||||
|
/// directly (response_type=token). When false, such requests will be rejected.
|
||||||
|
///
|
||||||
|
/// This property is typically set in the constructor and should not be
|
||||||
|
/// modified after initialization.
|
||||||
final bool allowsImplicit;
|
final bool allowsImplicit;
|
||||||
|
|
||||||
/// A randomly generated value the client can use to verify the origin of the redirect.
|
/// A randomly generated value the client can use to verify the origin of the redirect.
|
||||||
///
|
///
|
||||||
/// Clients must include this query parameter and verify that any redirects from this
|
/// This property is bound to the 'state' query parameter of the incoming request.
|
||||||
/// server have the same value for 'state' as passed in. This value is usually a randomly generated
|
/// It serves as a security measure to prevent cross-site request forgery (CSRF) attacks.
|
||||||
/// session identifier.
|
///
|
||||||
|
/// Clients must include this query parameter when initiating an authorization request.
|
||||||
|
/// Upon receiving a redirect from this server, clients should verify that the 'state'
|
||||||
|
/// value in the redirect matches the one they initially sent. This ensures that the
|
||||||
|
/// response is for the request they initiated and not for a malicious request.
|
||||||
|
///
|
||||||
|
/// The value of 'state' is typically a randomly generated string or session identifier.
|
||||||
|
/// It should be unique for each authorization request to maintain security.
|
||||||
|
///
|
||||||
|
/// Example usage:
|
||||||
|
/// ```
|
||||||
|
/// GET /authorize?response_type=code&client_id=CLIENT_ID&state=RANDOM_STATE
|
||||||
|
/// ```
|
||||||
@Bind.query("state")
|
@Bind.query("state")
|
||||||
String? state;
|
String? state;
|
||||||
|
|
||||||
/// Must be 'code' or 'token'.
|
/// The type of response requested from the authorization endpoint.
|
||||||
|
///
|
||||||
|
/// This property is bound to the 'response_type' query parameter of the incoming request.
|
||||||
|
/// It must be either 'code' or 'token':
|
||||||
|
/// - 'code': Indicates that the client is initiating the authorization code flow.
|
||||||
|
/// - 'token': Indicates that the client is initiating the implicit flow.
|
||||||
|
///
|
||||||
|
/// The value of this property determines the type of credential (authorization code or access token)
|
||||||
|
/// that will be issued upon successful authentication.
|
||||||
|
///
|
||||||
|
/// Note: The availability of the 'token' response type depends on the [allowsImplicit] setting
|
||||||
|
/// of the controller.
|
||||||
@Bind.query("response_type")
|
@Bind.query("response_type")
|
||||||
String? responseType;
|
String? responseType;
|
||||||
|
|
||||||
/// The client ID of the authenticating client.
|
/// The client ID of the authenticating client.
|
||||||
///
|
///
|
||||||
/// This must be a valid client ID according to [authServer].\
|
/// This property is bound to the 'client_id' query parameter of the incoming request.
|
||||||
|
/// It represents the unique identifier of the client application requesting authorization.
|
||||||
|
///
|
||||||
|
/// The client ID must be registered and valid according to the [authServer].
|
||||||
|
/// It is used to identify the client during the authorization process and to ensure
|
||||||
|
/// that only authorized clients can request access tokens or authorization codes.
|
||||||
|
///
|
||||||
|
/// This field is nullable, but typically required for most OAuth 2.0 flows.
|
||||||
|
/// If not provided in the request, it may lead to authorization failures.
|
||||||
|
///
|
||||||
|
/// Example usage in a request URL:
|
||||||
|
/// ```
|
||||||
|
/// GET /authorize?client_id=my_client_id&...
|
||||||
|
/// ```
|
||||||
@Bind.query("client_id")
|
@Bind.query("client_id")
|
||||||
String? clientID;
|
String? clientID;
|
||||||
|
|
||||||
/// Renders an HTML login form.
|
/// Delegate responsible for rendering the HTML login form.
|
||||||
|
///
|
||||||
|
/// If provided, this delegate will be used to generate a custom login page
|
||||||
|
/// when [getAuthorizationPage] is called. The delegate's [render] method
|
||||||
|
/// is responsible for creating the HTML content of the login form.
|
||||||
|
///
|
||||||
|
/// When this property is null, the controller will not serve a login page
|
||||||
|
/// and will respond with a 405 Method Not Allowed status for GET requests.
|
||||||
|
///
|
||||||
|
/// This delegate allows for customization of the login experience while
|
||||||
|
/// maintaining the required OAuth 2.0 authorization flow.
|
||||||
final AuthRedirectControllerDelegate? delegate;
|
final AuthRedirectControllerDelegate? delegate;
|
||||||
|
|
||||||
/// Returns an HTML login form.
|
/// Returns an HTML login form for OAuth 2.0 authorization.
|
||||||
///
|
///
|
||||||
/// A client that wishes to authenticate with this server should direct the user
|
/// A client that wishes to authenticate with this server should direct the user
|
||||||
/// to this page. The user will enter their username and password that is sent as a POST
|
/// to this page. The user will enter their username and password that is sent as a POST
|
||||||
|
@ -129,9 +242,10 @@ class AuthRedirectController extends ResourceController {
|
||||||
|
|
||||||
/// Creates a one-time use authorization code or an access token.
|
/// Creates a one-time use authorization code or an access token.
|
||||||
///
|
///
|
||||||
/// This method will respond with a redirect that either contains an authorization code ('code')
|
/// This method handles the OAuth 2.0 authorization process, responding with a redirect
|
||||||
/// or an access token ('token') along with the passed in 'state'. If this request fails,
|
/// that contains either an authorization code ('code') or an access token ('token')
|
||||||
/// the redirect URL will contain an 'error' instead of the authorization code or access token.
|
/// along with the passed in 'state'. If the request fails, the redirect URL will
|
||||||
|
/// contain an 'error' instead of the authorization code or access token.
|
||||||
///
|
///
|
||||||
/// This method is typically invoked by the login form returned from the GET to this controller.
|
/// This method is typically invoked by the login form returned from the GET to this controller.
|
||||||
@Operation.post()
|
@Operation.post()
|
||||||
|
@ -229,6 +343,19 @@ class AuthRedirectController extends ResourceController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Customizes the API documentation for the request body of this controller's operations.
|
||||||
|
///
|
||||||
|
/// This method overrides the default implementation to add specific details to the
|
||||||
|
/// POST operation's request body schema:
|
||||||
|
/// - Sets the format of the 'password' field to "password".
|
||||||
|
/// - Marks 'client_id', 'state', 'response_type', 'username', and 'password' as required fields.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - context: The API documentation context.
|
||||||
|
/// - operation: The operation being documented.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// The modified [APIRequestBody] object, or null if no modifications were made.
|
||||||
@override
|
@override
|
||||||
APIRequestBody? documentOperationRequestBody(
|
APIRequestBody? documentOperationRequestBody(
|
||||||
APIDocumentContext context,
|
APIDocumentContext context,
|
||||||
|
@ -249,6 +376,20 @@ class AuthRedirectController extends ResourceController {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Customizes the API documentation for the operation parameters of this controller.
|
||||||
|
///
|
||||||
|
/// This method overrides the default implementation to mark all parameters
|
||||||
|
/// as required, except for the 'scope' parameter. It does this by:
|
||||||
|
/// 1. Calling the superclass method to get the initial list of parameters.
|
||||||
|
/// 2. Iterating through all parameters except 'scope'.
|
||||||
|
/// 3. Setting the 'isRequired' property of each parameter to true.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - context: The API documentation context.
|
||||||
|
/// - operation: The operation being documented.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A list of [APIParameter] objects with updated 'isRequired' properties.
|
||||||
@override
|
@override
|
||||||
List<APIParameter> documentOperationParameters(
|
List<APIParameter> documentOperationParameters(
|
||||||
APIDocumentContext context,
|
APIDocumentContext context,
|
||||||
|
@ -261,6 +402,29 @@ class AuthRedirectController extends ResourceController {
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates API documentation for the responses of this controller's operations.
|
||||||
|
///
|
||||||
|
/// This method overrides the default implementation to provide custom documentation
|
||||||
|
/// for the GET and POST operations of the AuthRedirectController.
|
||||||
|
///
|
||||||
|
/// For GET requests:
|
||||||
|
/// - Documents a 200 response that serves a login form in HTML format.
|
||||||
|
///
|
||||||
|
/// For POST requests:
|
||||||
|
/// - Documents a 302 (Moved Temporarily) response for successful authorizations,
|
||||||
|
/// explaining the structure of the redirect URI for both 'code' and 'token' response types.
|
||||||
|
/// - Documents a 400 (Bad Request) response for cases where the client ID is invalid
|
||||||
|
/// and the redirect URI cannot be verified.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - context: The API documentation context.
|
||||||
|
/// - operation: The operation being documented.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A Map of status codes to APIResponse objects describing the possible responses.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// StateError if documentation fails (i.e., for unexpected HTTP methods).
|
||||||
@override
|
@override
|
||||||
Map<String, APIResponse> documentOperationResponses(
|
Map<String, APIResponse> documentOperationResponses(
|
||||||
APIDocumentContext context,
|
APIDocumentContext context,
|
||||||
|
@ -298,6 +462,24 @@ class AuthRedirectController extends ResourceController {
|
||||||
throw StateError("AuthRedirectController documentation failed.");
|
throw StateError("AuthRedirectController documentation failed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Customizes the API documentation for the operations of this controller.
|
||||||
|
///
|
||||||
|
/// This method overrides the default implementation to update the authorization URLs
|
||||||
|
/// for both the Authorization Code Flow and Implicit Flow in the API documentation.
|
||||||
|
///
|
||||||
|
/// It performs the following steps:
|
||||||
|
/// 1. Calls the superclass method to get the initial operations documentation.
|
||||||
|
/// 2. Constructs a URI from the given route.
|
||||||
|
/// 3. Sets this URI as the authorization URL for both the Authorization Code Flow
|
||||||
|
/// and the Implicit Flow in the auth server's documentation.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - context: The API documentation context.
|
||||||
|
/// - route: The route string for this controller.
|
||||||
|
/// - path: The APIPath object representing this controller's path.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A Map of operation IDs to APIOperation objects describing the operations of this controller.
|
||||||
@override
|
@override
|
||||||
Map<String, APIOperation> documentOperations(
|
Map<String, APIOperation> documentOperations(
|
||||||
APIDocumentContext context,
|
APIDocumentContext context,
|
||||||
|
@ -311,6 +493,27 @@ class AuthRedirectController extends ResourceController {
|
||||||
return ops;
|
return ops;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates a redirect response for OAuth 2.0 authorization flow.
|
||||||
|
///
|
||||||
|
/// This method constructs a redirect URI based on the given parameters and the type of response
|
||||||
|
/// (code or token) requested. It handles both successful authorizations and error cases.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [inputUri]: The base URI to redirect to. If null, falls back to the client's registered redirect URI.
|
||||||
|
/// - [clientStateOrNull]: The state parameter provided by the client for CSRF protection.
|
||||||
|
/// - [code]: The authorization code (for code flow).
|
||||||
|
/// - [token]: The access token (for token/implicit flow).
|
||||||
|
/// - [error]: Any error that occurred during the authorization process.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// - A [Response] object with a 302 status code and appropriate headers for redirection.
|
||||||
|
/// - If the redirect URI is invalid or cannot be constructed, returns a 400 Bad Request response.
|
||||||
|
///
|
||||||
|
/// The method constructs the redirect URI as follows:
|
||||||
|
/// - For 'code' response type: Adds code, state, and error (if any) as query parameters.
|
||||||
|
/// - For 'token' response type: Adds token details, state, and error (if any) as URI fragment.
|
||||||
|
///
|
||||||
|
/// The response includes headers to prevent caching of the redirect.
|
||||||
Response _redirectResponse(
|
Response _redirectResponse(
|
||||||
String? inputUri,
|
String? inputUri,
|
||||||
String? clientStateOrNull, {
|
String? clientStateOrNull, {
|
||||||
|
|
|
@ -1,5 +1,23 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
/// An abstract class for parsing authorization headers.
|
||||||
|
///
|
||||||
|
/// This class defines a common interface for parsing different types of
|
||||||
|
/// authorization headers. Implementations of this class should provide
|
||||||
|
/// specific parsing logic for different authorization schemes (e.g., Bearer, Basic).
|
||||||
|
///
|
||||||
|
/// The type parameter [T] represents the return type of the [parse] method,
|
||||||
|
/// allowing for flexibility in the parsed result (e.g., String for Bearer tokens,
|
||||||
|
/// custom credential objects for other schemes).
|
||||||
abstract class AuthorizationParser<T> {
|
abstract class AuthorizationParser<T> {
|
||||||
const AuthorizationParser();
|
const AuthorizationParser();
|
||||||
|
|
||||||
|
@ -7,11 +25,24 @@ abstract class AuthorizationParser<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses a Bearer token from an Authorization header.
|
/// Parses a Bearer token from an Authorization header.
|
||||||
|
///
|
||||||
|
/// This class extends [AuthorizationParser] and specializes in parsing Bearer tokens
|
||||||
|
/// from Authorization headers. It implements the [parse] method to extract the token
|
||||||
|
/// from a given header string.
|
||||||
|
///
|
||||||
|
/// Usage:
|
||||||
|
/// ```dart
|
||||||
|
/// final parser = AuthorizationBearerParser();
|
||||||
|
/// final token = parser.parse("Bearer myToken123");
|
||||||
|
/// print(token); // Outputs: myToken123
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// If the header is invalid or missing, it throws an [AuthorizationParserException]
|
||||||
|
/// with an appropriate [AuthorizationParserExceptionReason].
|
||||||
class AuthorizationBearerParser extends AuthorizationParser<String?> {
|
class AuthorizationBearerParser extends AuthorizationParser<String?> {
|
||||||
const AuthorizationBearerParser();
|
const AuthorizationBearerParser();
|
||||||
|
|
||||||
/// Parses a Bearer token from [authorizationHeader]. If the header is malformed or doesn't exist,
|
/// Parses a Bearer token from an Authorization header.
|
||||||
/// throws an [AuthorizationParserException]. Otherwise, returns the [String] representation of the bearer token.
|
|
||||||
///
|
///
|
||||||
/// For example, if the input to this method is "Bearer token" it would return 'token'.
|
/// For example, if the input to this method is "Bearer token" it would return 'token'.
|
||||||
///
|
///
|
||||||
|
@ -37,6 +68,26 @@ class AuthorizationBearerParser extends AuthorizationParser<String?> {
|
||||||
|
|
||||||
/// A structure to hold Basic authorization credentials.
|
/// A structure to hold Basic authorization credentials.
|
||||||
///
|
///
|
||||||
|
/// This class represents the credentials used in Basic HTTP Authentication.
|
||||||
|
/// It contains two properties: [username] and [password].
|
||||||
|
///
|
||||||
|
/// The [username] and [password] are marked as `late final`, indicating that
|
||||||
|
/// they must be initialized before use, but can only be set once.
|
||||||
|
///
|
||||||
|
/// This class is typically used in conjunction with [AuthorizationBasicParser]
|
||||||
|
/// to parse and store credentials from a Basic Authorization header.
|
||||||
|
///
|
||||||
|
/// The [toString] method is overridden to provide a string representation
|
||||||
|
/// of the credentials in the format "username:password".
|
||||||
|
///
|
||||||
|
/// Example usage:
|
||||||
|
/// ```dart
|
||||||
|
/// final credentials = AuthBasicCredentials()
|
||||||
|
/// ..username = 'john_doe'
|
||||||
|
/// ..password = 'secret123';
|
||||||
|
/// print(credentials); // Outputs: john_doe:secret123
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
/// See [AuthorizationBasicParser] for getting instances of this type.
|
/// See [AuthorizationBasicParser] for getting instances of this type.
|
||||||
class AuthBasicCredentials {
|
class AuthBasicCredentials {
|
||||||
/// The username of a Basic Authorization header.
|
/// The username of a Basic Authorization header.
|
||||||
|
@ -50,14 +101,38 @@ class AuthBasicCredentials {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses a Basic Authorization header.
|
/// Parses a Basic Authorization header.
|
||||||
|
///
|
||||||
|
/// This class extends [AuthorizationParser] and specializes in parsing Basic Authentication
|
||||||
|
/// credentials from Authorization headers. It implements the [parse] method to extract
|
||||||
|
/// the username and password from a given header string.
|
||||||
|
///
|
||||||
|
/// The parser expects the header to be in the format "Basic <base64-encoded-credentials>",
|
||||||
|
/// where the credentials are a string of "username:password" encoded in Base64.
|
||||||
|
///
|
||||||
|
/// Usage:
|
||||||
|
/// ```dart
|
||||||
|
/// final parser = AuthorizationBasicParser();
|
||||||
|
/// final credentials = parser.parse("Basic dXNlcm5hbWU6cGFzc3dvcmQ=");
|
||||||
|
/// print(credentials.username); // Outputs: username
|
||||||
|
/// print(credentials.password); // Outputs: password
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// If the header is invalid, missing, or cannot be properly decoded, it throws an
|
||||||
|
/// [AuthorizationParserException] with an appropriate [AuthorizationParserExceptionReason].
|
||||||
class AuthorizationBasicParser
|
class AuthorizationBasicParser
|
||||||
extends AuthorizationParser<AuthBasicCredentials> {
|
extends AuthorizationParser<AuthBasicCredentials> {
|
||||||
|
/// Creates a constant instance of [AuthorizationBasicParser].
|
||||||
|
///
|
||||||
|
/// This constructor allows for the creation of immutable instances of the parser,
|
||||||
|
/// which can be safely shared and reused across multiple parts of an application.
|
||||||
|
///
|
||||||
|
/// Example usage:
|
||||||
|
/// ```dart
|
||||||
|
/// final parser = const AuthorizationBasicParser();
|
||||||
|
/// ```
|
||||||
const AuthorizationBasicParser();
|
const AuthorizationBasicParser();
|
||||||
|
|
||||||
/// Returns a [AuthBasicCredentials] containing the username and password
|
/// Parses a Basic Authorization header and returns [AuthBasicCredentials].
|
||||||
/// base64 encoded in [authorizationHeader]. For example, if the input to this method
|
|
||||||
/// was 'Basic base64String' it would decode the base64String
|
|
||||||
/// and return the username and password by splitting that decoded string around the character ':'.
|
|
||||||
///
|
///
|
||||||
/// If [authorizationHeader] is malformed or null, throws an [AuthorizationParserException].
|
/// If [authorizationHeader] is malformed or null, throws an [AuthorizationParserException].
|
||||||
@override
|
@override
|
||||||
|
@ -100,10 +175,49 @@ class AuthorizationBasicParser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The reason either [AuthorizationBearerParser] or [AuthorizationBasicParser] failed.
|
/// Enumerates the possible reasons for authorization parsing failures.
|
||||||
|
///
|
||||||
|
/// This enum is used in conjunction with [AuthorizationParserException] to
|
||||||
|
/// provide more specific information about why the parsing of an authorization
|
||||||
|
/// header failed.
|
||||||
|
///
|
||||||
|
/// The enum contains two values:
|
||||||
|
/// - [missing]: Indicates that the required authorization header was not present.
|
||||||
|
/// - [malformed]: Indicates that the authorization header was present but its
|
||||||
|
/// format was incorrect or could not be properly parsed.
|
||||||
|
///
|
||||||
|
/// This enum is typically used by [AuthorizationBearerParser] and
|
||||||
|
/// [AuthorizationBasicParser] to specify the nature of parsing failures.
|
||||||
enum AuthorizationParserExceptionReason { missing, malformed }
|
enum AuthorizationParserExceptionReason { missing, malformed }
|
||||||
|
|
||||||
/// An exception indicating why Authorization parsing failed.
|
/// An exception class for errors encountered during authorization parsing.
|
||||||
|
///
|
||||||
|
/// This exception is thrown when there's an issue parsing an authorization header.
|
||||||
|
/// It contains a [reason] field of type [AuthorizationParserExceptionReason]
|
||||||
|
/// which provides more specific information about why the parsing failed.
|
||||||
|
///
|
||||||
|
/// The [reason] can be either [AuthorizationParserExceptionReason.missing]
|
||||||
|
/// (indicating the absence of a required authorization header) or
|
||||||
|
/// [AuthorizationParserExceptionReason.malformed] (indicating an incorrectly
|
||||||
|
/// formatted authorization header).
|
||||||
|
///
|
||||||
|
/// This exception is typically thrown by implementations of [AuthorizationParser],
|
||||||
|
/// such as [AuthorizationBearerParser] and [AuthorizationBasicParser].
|
||||||
|
///
|
||||||
|
/// Example usage:
|
||||||
|
/// ```dart
|
||||||
|
/// try {
|
||||||
|
/// parser.parse(header);
|
||||||
|
/// } catch (e) {
|
||||||
|
/// if (e is AuthorizationParserException) {
|
||||||
|
/// if (e.reason == AuthorizationParserExceptionReason.missing) {
|
||||||
|
/// print('Authorization header is missing');
|
||||||
|
/// } else if (e.reason == AuthorizationParserExceptionReason.malformed) {
|
||||||
|
/// print('Authorization header is malformed');
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
class AuthorizationParserException implements Exception {
|
class AuthorizationParserException implements Exception {
|
||||||
AuthorizationParserException(this.reason);
|
AuthorizationParserException(this.reason);
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
@ -8,9 +17,10 @@ import 'package:crypto/crypto.dart';
|
||||||
|
|
||||||
/// A OAuth 2.0 authorization server.
|
/// A OAuth 2.0 authorization server.
|
||||||
///
|
///
|
||||||
/// An [AuthServer] is an implementation of an OAuth 2.0 authorization server. An authorization server
|
/// This class implements the core functionality of an OAuth 2.0 authorization server,
|
||||||
/// issues, refreshes and revokes access tokens. It also verifies previously issued tokens, as
|
/// including client management, token issuance, token refresh, and token verification.
|
||||||
/// well as client and resource owner credentials.
|
/// It supports various OAuth 2.0 flows such as password, client credentials, authorization code,
|
||||||
|
/// and refresh token.
|
||||||
///
|
///
|
||||||
/// [AuthServer]s are typically used in conjunction with [AuthController] and [AuthRedirectController].
|
/// [AuthServer]s are typically used in conjunction with [AuthController] and [AuthRedirectController].
|
||||||
/// These controllers provide HTTP interfaces to the [AuthServer] for issuing and refreshing tokens.
|
/// These controllers provide HTTP interfaces to the [AuthServer] for issuing and refreshing tokens.
|
||||||
|
@ -57,9 +67,29 @@ import 'package:crypto/crypto.dart';
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
class AuthServer implements AuthValidator, APIComponentDocumenter {
|
class AuthServer implements AuthValidator, APIComponentDocumenter {
|
||||||
/// Creates a new instance of an [AuthServer] with a [delegate].
|
/// This constructor initializes an [AuthServer] with the provided [delegate],
|
||||||
|
/// which is responsible for managing authentication-related data storage and retrieval.
|
||||||
///
|
///
|
||||||
/// [hashFunction] defaults to [sha256].
|
/// Parameters:
|
||||||
|
/// - [delegate]: An instance of [AuthServerDelegate] that handles data persistence.
|
||||||
|
/// - [hashRounds]: The number of iterations for password hashing. Defaults to 1000.
|
||||||
|
/// - [hashLength]: The length of the generated hash in bytes. Defaults to 32.
|
||||||
|
/// - [hashFunction]: The hash function to use. Defaults to [sha256].
|
||||||
|
///
|
||||||
|
/// The [hashRounds], [hashLength], and [hashFunction] parameters configure the
|
||||||
|
/// password hashing mechanism used by this [AuthServer] instance. These values
|
||||||
|
/// affect the security and performance of password hashing operations.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// final delegate = MyAuthServerDelegate();
|
||||||
|
/// final authServer = AuthServer(
|
||||||
|
/// delegate,
|
||||||
|
/// hashRounds: 1000,
|
||||||
|
/// hashLength: 32,
|
||||||
|
/// hashFunction: sha256,
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
AuthServer(
|
AuthServer(
|
||||||
this.delegate, {
|
this.delegate, {
|
||||||
this.hashRounds = 1000,
|
this.hashRounds = 1000,
|
||||||
|
@ -74,32 +104,117 @@ class AuthServer implements AuthValidator, APIComponentDocumenter {
|
||||||
///
|
///
|
||||||
/// It is preferable to use the implementation of [AuthServerDelegate] from 'package:conduit_core/managed_auth.dart'. See
|
/// It is preferable to use the implementation of [AuthServerDelegate] from 'package:conduit_core/managed_auth.dart'. See
|
||||||
/// [AuthServer] for more details.
|
/// [AuthServer] for more details.
|
||||||
|
///
|
||||||
|
/// This delegate plays a crucial role in the OAuth 2.0 flow by managing the persistence
|
||||||
|
/// of authentication-related objects. It abstracts away the storage implementation,
|
||||||
|
/// allowing for flexibility in how these objects are stored (e.g., in-memory, database).
|
||||||
|
///
|
||||||
|
/// The delegate is responsible for the following main tasks:
|
||||||
|
/// 1. Storing and retrieving AuthClient information
|
||||||
|
/// 2. Managing AuthToken lifecycle (creation, retrieval, and revocation)
|
||||||
|
/// 3. Handling AuthCode operations for the authorization code flow
|
||||||
|
/// 4. Fetching ResourceOwner information for authentication purposes
|
||||||
|
///
|
||||||
|
/// Implementations of this delegate should ensure thread-safety and efficient
|
||||||
|
/// data access to maintain the performance and security of the authentication server.
|
||||||
final AuthServerDelegate delegate;
|
final AuthServerDelegate delegate;
|
||||||
|
|
||||||
/// The number of hashing rounds performed by this instance when validating a password.
|
/// The number of hashing rounds performed by this instance when validating a password.
|
||||||
|
///
|
||||||
|
/// This value determines the number of iterations the password hashing algorithm
|
||||||
|
/// will perform. A higher number of rounds increases the computational cost and
|
||||||
|
/// time required to hash a password, making it more resistant to brute-force attacks.
|
||||||
|
/// However, it also increases the time needed for legitimate password verification.
|
||||||
|
///
|
||||||
|
/// The optimal value balances security and performance based on the specific
|
||||||
|
/// requirements of the application. Common values range from 1000 to 50000,
|
||||||
|
/// but may need adjustment based on hardware capabilities and security needs.
|
||||||
final int hashRounds;
|
final int hashRounds;
|
||||||
|
|
||||||
/// The resulting key length of a password hash when generated by this instance.
|
/// The resulting key length of a password hash when generated by this instance.
|
||||||
|
///
|
||||||
|
/// This value determines the length (in bytes) of the generated password hash.
|
||||||
|
/// A longer hash length generally provides more security against certain types of attacks,
|
||||||
|
/// but also requires more storage space. Common values range from 16 to 64 bytes.
|
||||||
|
///
|
||||||
|
/// This parameter is used in conjunction with [hashRounds] and [hashFunction]
|
||||||
|
/// to configure the password hashing algorithm (typically PBKDF2).
|
||||||
final int hashLength;
|
final int hashLength;
|
||||||
|
|
||||||
/// The [Hash] function used by the PBKDF2 algorithm to generate password hashes by this instance.
|
/// The [Hash] function used by the PBKDF2 algorithm to generate password hashes by this instance.
|
||||||
|
///
|
||||||
|
/// This function is used in the password hashing process to create secure, one-way
|
||||||
|
/// hashes of passwords. The PBKDF2 (Password-Based Key Derivation Function 2)
|
||||||
|
/// algorithm uses this hash function repeatedly to increase the computational cost
|
||||||
|
/// of cracking the resulting hash.
|
||||||
|
///
|
||||||
|
/// By default, this is set to [sha256], but it can be customized to use other
|
||||||
|
/// cryptographic hash functions if needed. The choice of hash function affects
|
||||||
|
/// the security and performance characteristics of the password hashing process.
|
||||||
|
///
|
||||||
|
/// This property works in conjunction with [hashRounds] and [hashLength] to
|
||||||
|
/// configure the overall password hashing strategy of the AuthServer.
|
||||||
final Hash hashFunction;
|
final Hash hashFunction;
|
||||||
|
|
||||||
/// Used during OpenAPI documentation.
|
/// Represents the OAuth 2.0 Authorization Code flow for OpenAPI documentation purposes.
|
||||||
|
///
|
||||||
|
/// This property is used to document the Authorization Code flow in the OpenAPI
|
||||||
|
/// specification generated for this AuthServer. It is initialized as an empty
|
||||||
|
/// OAuth2 flow with an empty scopes map, which can be populated later with
|
||||||
|
/// the specific scopes supported by the server.
|
||||||
|
///
|
||||||
|
/// The Authorization Code flow is a secure way of obtaining access tokens
|
||||||
|
/// that involves a client application directing the resource owner to an
|
||||||
|
/// authorization server to grant permission, then using the resulting
|
||||||
|
/// authorization code to obtain an access token.
|
||||||
|
///
|
||||||
|
/// This property is typically used in conjunction with the `documentComponents`
|
||||||
|
/// method to properly document the OAuth2 security scheme in the API specification.
|
||||||
final APISecuritySchemeOAuth2Flow documentedAuthorizationCodeFlow =
|
final APISecuritySchemeOAuth2Flow documentedAuthorizationCodeFlow =
|
||||||
APISecuritySchemeOAuth2Flow.empty()..scopes = {};
|
APISecuritySchemeOAuth2Flow.empty()..scopes = {};
|
||||||
|
|
||||||
/// Used during OpenAPI documentation.
|
/// Represents the OAuth 2.0 Password flow for OpenAPI documentation purposes.
|
||||||
|
///
|
||||||
|
/// This property is used to document the Password flow in the OpenAPI
|
||||||
|
/// specification generated for this AuthServer. It is initialized as an empty
|
||||||
|
/// OAuth2 flow with an empty scopes map, which can be populated later with
|
||||||
|
/// the specific scopes supported by the server for the Password flow.
|
||||||
|
///
|
||||||
|
/// The Password flow allows users to exchange their username and password
|
||||||
|
/// directly for an access token. This flow should only be used by trusted
|
||||||
|
/// applications due to its sensitivity in handling user credentials.
|
||||||
|
///
|
||||||
|
/// This property is typically used in conjunction with the `documentComponents`
|
||||||
|
/// method to properly document the OAuth2 security scheme in the API specification.
|
||||||
final APISecuritySchemeOAuth2Flow documentedPasswordFlow =
|
final APISecuritySchemeOAuth2Flow documentedPasswordFlow =
|
||||||
APISecuritySchemeOAuth2Flow.empty()..scopes = {};
|
APISecuritySchemeOAuth2Flow.empty()..scopes = {};
|
||||||
|
|
||||||
/// Used during OpenAPI documentation.
|
/// Represents the OAuth 2.0 Implicit flow for OpenAPI documentation purposes.
|
||||||
|
///
|
||||||
|
/// This property is used to document the Implicit flow in the OpenAPI
|
||||||
|
/// specification generated for this AuthServer. It is initialized as an empty
|
||||||
|
/// OAuth2 flow with an empty scopes map, which can be populated later with
|
||||||
|
/// the specific scopes supported by the server for the Implicit flow.
|
||||||
|
///
|
||||||
|
/// The Implicit flow is designed for client-side applications (e.g., single-page web apps)
|
||||||
|
/// where the access token is returned immediately without an extra authorization code
|
||||||
|
/// exchange step. This flow has some security trade-offs and is generally not recommended
|
||||||
|
/// for new implementations.
|
||||||
|
///
|
||||||
|
/// This property is typically used in conjunction with the `documentComponents`
|
||||||
|
/// method to properly document the OAuth2 security scheme in the API specification.
|
||||||
final APISecuritySchemeOAuth2Flow documentedImplicitFlow =
|
final APISecuritySchemeOAuth2Flow documentedImplicitFlow =
|
||||||
APISecuritySchemeOAuth2Flow.empty()..scopes = {};
|
APISecuritySchemeOAuth2Flow.empty()..scopes = {};
|
||||||
|
|
||||||
|
/// Constant representing the token type "bearer" for OAuth 2.0 access tokens.
|
||||||
|
///
|
||||||
|
/// This value is used to specify the type of token issued by the authorization server.
|
||||||
|
/// The "bearer" token type is defined in RFC 6750 and is the most common type used in OAuth 2.0.
|
||||||
|
/// Bearer tokens can be used by any party in possession of the token to access protected resources
|
||||||
|
/// without demonstrating possession of a cryptographic key.
|
||||||
static const String tokenTypeBearer = "bearer";
|
static const String tokenTypeBearer = "bearer";
|
||||||
|
|
||||||
/// Hashes a [password] with [salt] using PBKDF2 algorithm.
|
/// Hashes a password using the PBKDF2 algorithm.
|
||||||
///
|
///
|
||||||
/// See [hashRounds], [hashLength] and [hashFunction] for more details. This method
|
/// See [hashRounds], [hashLength] and [hashFunction] for more details. This method
|
||||||
/// invoke [auth.generatePasswordHash] with the above inputs.
|
/// invoke [auth.generatePasswordHash] with the above inputs.
|
||||||
|
@ -113,7 +228,7 @@ class AuthServer implements AuthValidator, APIComponentDocumenter {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds an OAuth2 client.
|
/// Adds a new OAuth2 client to the authentication server.
|
||||||
///
|
///
|
||||||
/// [delegate] will store this client for future use.
|
/// [delegate] will store this client for future use.
|
||||||
Future addClient(AuthClient client) async {
|
Future addClient(AuthClient client) async {
|
||||||
|
@ -132,14 +247,14 @@ class AuthServer implements AuthValidator, APIComponentDocumenter {
|
||||||
return delegate.addClient(this, client);
|
return delegate.addClient(this, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a [AuthClient] record for its [clientID].
|
/// Retrieves an [AuthClient] record based on the provided [clientID].
|
||||||
///
|
///
|
||||||
/// Returns null if none exists.
|
/// Returns null if none exists.
|
||||||
Future<AuthClient?> getClient(String clientID) async {
|
Future<AuthClient?> getClient(String clientID) async {
|
||||||
return delegate.getClient(this, clientID);
|
return delegate.getClient(this, clientID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Revokes a [AuthClient] record.
|
/// Revokes and removes an [AuthClient] record associated with the given [clientID].
|
||||||
///
|
///
|
||||||
/// Removes cached occurrences of [AuthClient] for [clientID].
|
/// Removes cached occurrences of [AuthClient] for [clientID].
|
||||||
/// Asks [delegate] to remove an [AuthClient] by its ID via [AuthServerDelegate.removeClient].
|
/// Asks [delegate] to remove an [AuthClient] by its ID via [AuthServerDelegate.removeClient].
|
||||||
|
@ -151,7 +266,7 @@ class AuthServer implements AuthValidator, APIComponentDocumenter {
|
||||||
return delegate.removeClient(this, clientID);
|
return delegate.removeClient(this, clientID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Revokes access for an [ResourceOwner].
|
/// Revokes all access grants for a specific resource owner.
|
||||||
///
|
///
|
||||||
/// All authorization codes and tokens for the [ResourceOwner] identified by [identifier]
|
/// All authorization codes and tokens for the [ResourceOwner] identified by [identifier]
|
||||||
/// will be revoked.
|
/// will be revoked.
|
||||||
|
@ -163,7 +278,7 @@ class AuthServer implements AuthValidator, APIComponentDocumenter {
|
||||||
await delegate.removeTokens(this, identifier);
|
await delegate.removeTokens(this, identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Authenticates a username and password of an [ResourceOwner] and returns an [AuthToken] upon success.
|
/// Authenticates a username and password of a [ResourceOwner] and returns an [AuthToken] upon success.
|
||||||
///
|
///
|
||||||
/// This method works with this instance's [delegate] to generate and store a new token if all credentials are correct.
|
/// This method works with this instance's [delegate] to generate and store a new token if all credentials are correct.
|
||||||
/// If credentials are not correct, it will throw the appropriate [AuthRequestError].
|
/// If credentials are not correct, it will throw the appropriate [AuthRequestError].
|
||||||
|
@ -230,7 +345,7 @@ class AuthServer implements AuthValidator, APIComponentDocumenter {
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a [Authorization] for [accessToken].
|
/// Verifies the validity of an access token and returns an [Authorization] object.
|
||||||
///
|
///
|
||||||
/// This method obtains an [AuthToken] for [accessToken] from [delegate] and then verifies that the token is valid.
|
/// This method obtains an [AuthToken] for [accessToken] from [delegate] and then verifies that the token is valid.
|
||||||
/// If the token is valid, an [Authorization] object is returned. Otherwise, an [AuthServerException] is thrown.
|
/// If the token is valid, an [Authorization] object is returned. Otherwise, an [AuthServerException] is thrown.
|
||||||
|
@ -269,9 +384,34 @@ class AuthServer implements AuthValidator, APIComponentDocumenter {
|
||||||
|
|
||||||
/// Refreshes a valid [AuthToken] instance.
|
/// Refreshes a valid [AuthToken] instance.
|
||||||
///
|
///
|
||||||
/// This method will refresh a [AuthToken] given the [AuthToken]'s [refreshToken] for a given client ID.
|
/// This method refreshes an existing [AuthToken] using its [refreshToken] for a given client ID.
|
||||||
/// This method coordinates with this instance's [delegate] to update the old token with a new access token and issue/expiration dates if successful.
|
/// It coordinates with the instance's [delegate] to update the old token with a new access token
|
||||||
/// If not successful, it will throw an [AuthRequestError].
|
/// and issue/expiration dates if successful. If unsuccessful, it throws an [AuthRequestError].
|
||||||
|
///
|
||||||
|
/// The method performs several validation steps:
|
||||||
|
/// 1. Verifies the client ID and retrieves the corresponding [AuthClient].
|
||||||
|
/// 2. Checks for the presence of a refresh token.
|
||||||
|
/// 3. Retrieves the existing token using the refresh token.
|
||||||
|
/// 4. Validates the client secret.
|
||||||
|
/// 5. Handles scope validation and updates:
|
||||||
|
/// - If new scopes are requested, it ensures they are subsets of existing scopes and allowed by the client.
|
||||||
|
/// - If no new scopes are requested, it verifies that existing scopes are still valid for the client.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [refreshToken]: The refresh token of the [AuthToken] to be refreshed.
|
||||||
|
/// - [clientID]: The ID of the client requesting the token refresh.
|
||||||
|
/// - [clientSecret]: The secret of the client requesting the token refresh.
|
||||||
|
/// - [requestedScopes]: Optional list of scopes to be applied to the refreshed token.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A [Future] that resolves to a new [AuthToken] with updated access token, issue date, and expiration date.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// - [AuthServerException] with [AuthRequestError.invalidClient] if the client ID is invalid or empty.
|
||||||
|
/// - [AuthServerException] with [AuthRequestError.invalidRequest] if the refresh token is missing.
|
||||||
|
/// - [AuthServerException] with [AuthRequestError.invalidGrant] if the token is not found or doesn't match the client ID.
|
||||||
|
/// - [AuthServerException] with [AuthRequestError.invalidClient] if the client secret is invalid.
|
||||||
|
/// - [AuthServerException] with [AuthRequestError.invalidScope] if the requested scopes are invalid or not allowed.
|
||||||
Future<AuthToken> refresh(
|
Future<AuthToken> refresh(
|
||||||
String? refreshToken,
|
String? refreshToken,
|
||||||
String clientID,
|
String clientID,
|
||||||
|
@ -356,9 +496,34 @@ class AuthServer implements AuthValidator, APIComponentDocumenter {
|
||||||
|
|
||||||
/// Creates a one-time use authorization code for a given client ID and user credentials.
|
/// Creates a one-time use authorization code for a given client ID and user credentials.
|
||||||
///
|
///
|
||||||
/// This methods works with this instance's [delegate] to generate and store the authorization code
|
/// This method is part of the OAuth 2.0 Authorization Code flow. It authenticates a user
|
||||||
/// if the credentials are correct. If they are not correct, it will throw the
|
/// with their username and password for a specific client, and if successful, generates
|
||||||
/// appropriate [AuthRequestError].
|
/// a short-lived authorization code.
|
||||||
|
///
|
||||||
|
/// The method performs several steps:
|
||||||
|
/// 1. Validates the client ID and retrieves the client information.
|
||||||
|
/// 2. Authenticates the user with the provided username and password.
|
||||||
|
/// 3. Validates the requested scopes against the client's allowed scopes and the user's permissions.
|
||||||
|
/// 4. Generates a new authorization code.
|
||||||
|
/// 5. Stores the authorization code using the delegate.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [username]: The username of the resource owner (user).
|
||||||
|
/// - [password]: The password of the resource owner.
|
||||||
|
/// - [clientID]: The ID of the client requesting the authorization code.
|
||||||
|
/// - [expirationInSeconds]: The lifetime of the authorization code in seconds (default is 600 seconds or 10 minutes).
|
||||||
|
/// - [requestedScopes]: Optional list of scopes the client is requesting access to.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A [Future] that resolves to an [AuthCode] object representing the generated authorization code.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// - [AuthServerException] with [AuthRequestError.invalidClient] if the client ID is invalid or empty.
|
||||||
|
/// - [AuthServerException] with [AuthRequestError.invalidRequest] if the username or password is missing.
|
||||||
|
/// - [AuthServerException] with [AuthRequestError.unauthorizedClient] if the client doesn't have a redirect URI.
|
||||||
|
/// - [AuthServerException] with [AuthRequestError.accessDenied] if the user credentials are invalid.
|
||||||
|
///
|
||||||
|
/// The generated authorization code can later be exchanged for an access token using the `exchange` method.
|
||||||
Future<AuthCode> authenticateForCode(
|
Future<AuthCode> authenticateForCode(
|
||||||
String? username,
|
String? username,
|
||||||
String? password,
|
String? password,
|
||||||
|
@ -408,9 +573,35 @@ class AuthServer implements AuthValidator, APIComponentDocumenter {
|
||||||
|
|
||||||
/// Exchanges a valid authorization code for an [AuthToken].
|
/// Exchanges a valid authorization code for an [AuthToken].
|
||||||
///
|
///
|
||||||
/// If the authorization code has not expired, has not been used, matches the client ID,
|
/// This method is part of the OAuth 2.0 Authorization Code flow. It allows a client
|
||||||
/// and the client secret is correct, it will return a valid [AuthToken]. Otherwise,
|
/// to exchange a previously obtained authorization code for an access token.
|
||||||
/// it will throw an appropriate [AuthRequestError].
|
///
|
||||||
|
/// The method performs several validation steps:
|
||||||
|
/// 1. Verifies the client ID and retrieves the corresponding [AuthClient].
|
||||||
|
/// 2. Checks for the presence of the authorization code.
|
||||||
|
/// 3. Validates the client secret.
|
||||||
|
/// 4. Retrieves and validates the stored authorization code.
|
||||||
|
/// 5. Checks if the authorization code is still valid and hasn't been used.
|
||||||
|
/// 6. Ensures the client ID matches the one associated with the authorization code.
|
||||||
|
///
|
||||||
|
/// If all validations pass, it generates a new access token and stores it using the delegate.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [authCodeString]: The authorization code to be exchanged.
|
||||||
|
/// - [clientID]: The ID of the client requesting the token exchange.
|
||||||
|
/// - [clientSecret]: The secret of the client requesting the token exchange.
|
||||||
|
/// - [expirationInSeconds]: The lifetime of the generated access token in seconds (default is 3600 seconds or 1 hour).
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A [Future] that resolves to an [AuthToken] representing the newly created access token.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// - [AuthServerException] with [AuthRequestError.invalidClient] if the client ID is invalid or empty, or if the client secret is incorrect.
|
||||||
|
/// - [AuthServerException] with [AuthRequestError.invalidRequest] if the authorization code is missing.
|
||||||
|
/// - [AuthServerException] with [AuthRequestError.invalidGrant] if the authorization code is invalid, expired, or has been used before.
|
||||||
|
///
|
||||||
|
/// This method is crucial for completing the Authorization Code flow, allowing clients
|
||||||
|
/// to securely obtain access tokens after receiving user authorization.
|
||||||
Future<AuthToken> exchange(
|
Future<AuthToken> exchange(
|
||||||
String? authCodeString,
|
String? authCodeString,
|
||||||
String clientID,
|
String clientID,
|
||||||
|
@ -474,6 +665,22 @@ class AuthServer implements AuthValidator, APIComponentDocumenter {
|
||||||
//////
|
//////
|
||||||
// APIDocumentable overrides
|
// APIDocumentable overrides
|
||||||
//////
|
//////
|
||||||
|
|
||||||
|
/// Generates and registers security schemes for API documentation.
|
||||||
|
///
|
||||||
|
/// This method is responsible for documenting the security components of the API,
|
||||||
|
/// specifically the OAuth2 client authentication and standard OAuth2 flows.
|
||||||
|
///
|
||||||
|
/// It performs the following tasks:
|
||||||
|
/// 1. Registers a basic HTTP authentication scheme for OAuth2 client authentication.
|
||||||
|
/// 2. Creates and registers an OAuth2 security scheme with authorization code and password flows.
|
||||||
|
/// 3. Defers cleanup of unused flows based on the presence of required URLs.
|
||||||
|
///
|
||||||
|
/// The method uses the [APIDocumentContext] to register these security schemes,
|
||||||
|
/// making them available for use in the API documentation.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [context]: The [APIDocumentContext] used to register security schemes and defer cleanup operations.
|
||||||
@override
|
@override
|
||||||
void documentComponents(APIDocumentContext context) {
|
void documentComponents(APIDocumentContext context) {
|
||||||
final basic = APISecurityScheme.http("basic")
|
final basic = APISecurityScheme.http("basic")
|
||||||
|
@ -508,6 +715,27 @@ class AuthServer implements AuthValidator, APIComponentDocumenter {
|
||||||
/////
|
/////
|
||||||
// AuthValidator overrides
|
// AuthValidator overrides
|
||||||
/////
|
/////
|
||||||
|
|
||||||
|
/// Documents the security requirements for an [Authorizer] in the API specification.
|
||||||
|
///
|
||||||
|
/// This method generates the appropriate [APISecurityRequirement] objects
|
||||||
|
/// based on the type of authorization parser used by the [Authorizer].
|
||||||
|
///
|
||||||
|
/// For basic authentication (AuthorizationBasicParser), it specifies the
|
||||||
|
/// requirement for OAuth2 client authentication.
|
||||||
|
///
|
||||||
|
/// For bearer token authentication (AuthorizationBearerParser), it specifies
|
||||||
|
/// the requirement for OAuth2 with optional scopes.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [context]: The API documentation context.
|
||||||
|
/// - [authorizer]: The Authorizer instance for which to generate requirements.
|
||||||
|
/// - [scopes]: Optional list of scopes to be included in the OAuth2 requirement.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A list of [APISecurityRequirement] objects representing the security
|
||||||
|
/// requirements for the given authorizer. Returns an empty list if the
|
||||||
|
/// parser type is not recognized.
|
||||||
@override
|
@override
|
||||||
List<APISecurityRequirement> documentRequirementsForAuthorizer(
|
List<APISecurityRequirement> documentRequirementsForAuthorizer(
|
||||||
APIDocumentContext context,
|
APIDocumentContext context,
|
||||||
|
@ -529,6 +757,25 @@ class AuthServer implements AuthValidator, APIComponentDocumenter {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validates an authorization request using the specified parser and authorization data.
|
||||||
|
///
|
||||||
|
/// This method is responsible for validating different types of authorization,
|
||||||
|
/// including client credentials (Basic) and bearer tokens.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [parser]: An instance of [AuthorizationParser] used to parse the authorization data.
|
||||||
|
/// - [authorizationData]: The authorization data to be validated, type depends on the parser.
|
||||||
|
/// - [requiredScope]: Optional list of [AuthScope]s required for the authorization.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A [FutureOr<Authorization>] representing the validated authorization.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// - [ArgumentError] if an invalid parser is provided.
|
||||||
|
///
|
||||||
|
/// The method behaves differently based on the type of parser:
|
||||||
|
/// - For [AuthorizationBasicParser], it validates client credentials.
|
||||||
|
/// - For [AuthorizationBearerParser], it verifies the bearer token.
|
||||||
@override
|
@override
|
||||||
FutureOr<Authorization> validate<T>(
|
FutureOr<Authorization> validate<T>(
|
||||||
AuthorizationParser<T> parser,
|
AuthorizationParser<T> parser,
|
||||||
|
@ -547,6 +794,31 @@ class AuthServer implements AuthValidator, APIComponentDocumenter {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validates client credentials for OAuth 2.0 client authentication.
|
||||||
|
///
|
||||||
|
/// This method is used to authenticate a client using its client ID and secret
|
||||||
|
/// as part of the OAuth 2.0 client authentication process.
|
||||||
|
///
|
||||||
|
/// The method performs the following steps:
|
||||||
|
/// 1. Retrieves the client using the provided client ID (username).
|
||||||
|
/// 2. Validates the client's existence and secret.
|
||||||
|
/// 3. For public clients (no secret), it allows authentication with an empty password.
|
||||||
|
/// 4. For confidential clients, it verifies the provided password against the stored hashed secret.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [credentials]: An [AuthBasicCredentials] object containing the client ID (username) and secret (password).
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A [Future<Authorization>] representing the authenticated client.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// - [AuthServerException] with [AuthRequestError.invalidClient] if:
|
||||||
|
/// - The client is not found.
|
||||||
|
/// - A public client provides a non-empty password.
|
||||||
|
/// - A confidential client provides an incorrect secret.
|
||||||
|
///
|
||||||
|
/// This method is typically used in the context of the client credentials grant type
|
||||||
|
/// or when a client needs to authenticate itself for other OAuth 2.0 flows.
|
||||||
Future<Authorization> _validateClientCredentials(
|
Future<Authorization> _validateClientCredentials(
|
||||||
AuthBasicCredentials credentials,
|
AuthBasicCredentials credentials,
|
||||||
) async {
|
) async {
|
||||||
|
@ -574,6 +846,29 @@ class AuthServer implements AuthValidator, APIComponentDocumenter {
|
||||||
return Authorization(client.id, null, this, credentials: credentials);
|
return Authorization(client.id, null, this, credentials: credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validates and filters the requested scopes for a client and resource owner.
|
||||||
|
///
|
||||||
|
/// This method checks the requested scopes against the client's allowed scopes
|
||||||
|
/// and the resource owner's permitted scopes. It ensures that only valid and
|
||||||
|
/// authorized scopes are granted.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [client]: The [AuthClient] requesting the scopes.
|
||||||
|
/// - [authenticatable]: The [ResourceOwner] being authenticated.
|
||||||
|
/// - [requestedScopes]: The list of [AuthScope]s requested by the client.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A list of validated [AuthScope]s that are allowed for both the client and
|
||||||
|
/// the resource owner. Returns null if the client doesn't support scopes.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// - [AuthServerException] with [AuthRequestError.invalidScope] if:
|
||||||
|
/// - The client supports scopes but no scopes are requested.
|
||||||
|
/// - None of the requested scopes are allowed for the client.
|
||||||
|
/// - The filtered scopes are not allowed for the resource owner.
|
||||||
|
///
|
||||||
|
/// This method is crucial for maintaining the principle of least privilege
|
||||||
|
/// in OAuth 2.0 flows by ensuring that tokens are issued with appropriate scopes.
|
||||||
List<AuthScope>? _validatedScopes(
|
List<AuthScope>? _validatedScopes(
|
||||||
AuthClient client,
|
AuthClient client,
|
||||||
ResourceOwner authenticatable,
|
ResourceOwner authenticatable,
|
||||||
|
@ -611,6 +906,24 @@ class AuthServer implements AuthValidator, APIComponentDocumenter {
|
||||||
return validScopes;
|
return validScopes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates a new [AuthToken] with the specified parameters.
|
||||||
|
///
|
||||||
|
/// This method creates and initializes a new [AuthToken] object with the given
|
||||||
|
/// owner ID, client ID, and expiration time. It also sets other properties such
|
||||||
|
/// as the access token, issue date, token type, and optional refresh token.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [ownerID]: The identifier of the resource owner (user).
|
||||||
|
/// - [clientID]: The identifier of the client application.
|
||||||
|
/// - [expirationInSeconds]: The number of seconds until the token expires.
|
||||||
|
/// - [allowRefresh]: Whether to generate a refresh token (default is true).
|
||||||
|
/// - [scopes]: Optional list of scopes associated with the token.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A new [AuthToken] instance with all properties set according to the input parameters.
|
||||||
|
///
|
||||||
|
/// The access token and refresh token (if allowed) are generated as random strings.
|
||||||
|
/// The token type is set to "bearer" as defined by [tokenTypeBearer].
|
||||||
AuthToken _generateToken(
|
AuthToken _generateToken(
|
||||||
int? ownerID,
|
int? ownerID,
|
||||||
String clientID,
|
String clientID,
|
||||||
|
@ -635,6 +948,24 @@ class AuthServer implements AuthValidator, APIComponentDocumenter {
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates a new [AuthCode] with the specified parameters.
|
||||||
|
///
|
||||||
|
/// This method creates and initializes a new [AuthCode] object with the given
|
||||||
|
/// owner ID, client, and expiration time. It also sets other properties such
|
||||||
|
/// as the authorization code, issue date, and optional scopes.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [ownerID]: The identifier of the resource owner (user).
|
||||||
|
/// - [client]: The [AuthClient] for which the auth code is being generated.
|
||||||
|
/// - [expirationInSeconds]: The number of seconds until the auth code expires.
|
||||||
|
/// - [scopes]: Optional list of scopes associated with the auth code.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A new [AuthCode] instance with all properties set according to the input parameters.
|
||||||
|
///
|
||||||
|
/// The authorization code is generated as a random string of 32 characters.
|
||||||
|
/// The issue date is set to the current UTC time, and the expiration date is
|
||||||
|
/// calculated based on the [expirationInSeconds] parameter.
|
||||||
AuthCode _generateAuthCode(
|
AuthCode _generateAuthCode(
|
||||||
int? ownerID,
|
int? ownerID,
|
||||||
AuthClient client,
|
AuthClient client,
|
||||||
|
@ -652,6 +983,19 @@ class AuthServer implements AuthValidator, APIComponentDocumenter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates a random string of specified length.
|
||||||
|
///
|
||||||
|
/// This function creates a random string using a combination of uppercase letters,
|
||||||
|
/// lowercase letters, and digits. It uses a cryptographically secure random number
|
||||||
|
/// generator to ensure unpredictability.
|
||||||
|
///
|
||||||
|
/// The function works by repeatedly selecting random characters from a predefined
|
||||||
|
/// set of possible characters and appending them to a string buffer. The selection
|
||||||
|
/// process uses the modulo operation to ensure an even distribution across the
|
||||||
|
/// character set.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A string of the specified [length] containing random characters.
|
||||||
String randomStringOfLength(int length) {
|
String randomStringOfLength(int length) {
|
||||||
const possibleCharacters =
|
const possibleCharacters =
|
||||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
@ -8,8 +17,9 @@ import 'package:protevus_openapi/v3.dart';
|
||||||
|
|
||||||
/// A [Controller] that validates the Authorization header of a request.
|
/// A [Controller] that validates the Authorization header of a request.
|
||||||
///
|
///
|
||||||
/// An instance of this type will validate that the authorization information in an Authorization header is sufficient to access
|
/// This class, Authorizer, is responsible for authenticating and authorizing incoming HTTP requests.
|
||||||
/// the next controller in the channel.
|
/// It validates the Authorization header, processes it according to the specified parser (e.g., Bearer or Basic),
|
||||||
|
/// and then uses the provided validator to check the credentials.
|
||||||
///
|
///
|
||||||
/// For each request, this controller parses the authorization header, validates it with an [AuthValidator] and then create an [Authorization] object
|
/// For each request, this controller parses the authorization header, validates it with an [AuthValidator] and then create an [Authorization] object
|
||||||
/// if successful. The [Request] keeps a reference to this [Authorization] and is then sent to the next controller in the channel.
|
/// if successful. The [Request] keeps a reference to this [Authorization] and is then sent to the next controller in the channel.
|
||||||
|
@ -28,7 +38,7 @@ import 'package:protevus_openapi/v3.dart';
|
||||||
class Authorizer extends Controller {
|
class Authorizer extends Controller {
|
||||||
/// Creates an instance of [Authorizer].
|
/// Creates an instance of [Authorizer].
|
||||||
///
|
///
|
||||||
/// Use this constructor to provide custom [AuthorizationParser]s.
|
/// This constructor allows for creating an [Authorizer] with custom configurations.
|
||||||
///
|
///
|
||||||
/// By default, this instance will parse bearer tokens from the authorization header, e.g.:
|
/// By default, this instance will parse bearer tokens from the authorization header, e.g.:
|
||||||
///
|
///
|
||||||
|
@ -43,7 +53,11 @@ class Authorizer extends Controller {
|
||||||
|
|
||||||
/// Creates an instance of [Authorizer] with Basic Authentication parsing.
|
/// Creates an instance of [Authorizer] with Basic Authentication parsing.
|
||||||
///
|
///
|
||||||
/// Parses a username and password from the request's Basic Authentication data in the Authorization header, e.g.:
|
/// This constructor initializes an [Authorizer] that uses Basic Authentication.
|
||||||
|
/// It sets up the [Authorizer] to parse the Authorization header of incoming requests
|
||||||
|
/// using the [AuthorizationBasicParser].
|
||||||
|
///
|
||||||
|
/// The Authorization header for Basic Authentication should be in the format:
|
||||||
///
|
///
|
||||||
/// Authorization: Basic base64(username:password)
|
/// Authorization: Basic base64(username:password)
|
||||||
Authorizer.basic(AuthValidator? validator)
|
Authorizer.basic(AuthValidator? validator)
|
||||||
|
@ -51,7 +65,9 @@ class Authorizer extends Controller {
|
||||||
|
|
||||||
/// Creates an instance of [Authorizer] with Bearer token parsing.
|
/// Creates an instance of [Authorizer] with Bearer token parsing.
|
||||||
///
|
///
|
||||||
/// Parses a bearer token from the request's Authorization header, e.g.
|
/// This constructor initializes an [Authorizer] that uses Bearer token authentication.
|
||||||
|
/// It sets up the [Authorizer] to parse the Authorization header of incoming requests
|
||||||
|
/// using the [AuthorizationBearerParser].
|
||||||
///
|
///
|
||||||
/// Authorization: Bearer ap9ijlarlkz8jIOa9laweo
|
/// Authorization: Bearer ap9ijlarlkz8jIOa9laweo
|
||||||
///
|
///
|
||||||
|
@ -65,12 +81,19 @@ class Authorizer extends Controller {
|
||||||
|
|
||||||
/// The validating authorization object.
|
/// The validating authorization object.
|
||||||
///
|
///
|
||||||
/// This object will check credentials parsed from the Authorization header and produce an
|
/// This property holds an instance of [AuthValidator] responsible for validating
|
||||||
/// [Authorization] object representing the authorization the credentials have. It may also
|
/// the credentials parsed from the Authorization header. It processes these
|
||||||
/// reject a request. This is typically an instance of [AuthServer].
|
/// credentials and produces an [Authorization] object that represents the
|
||||||
|
/// authorization level of the provided credentials.
|
||||||
|
///
|
||||||
|
/// The validator can also reject a request if the credentials are invalid or
|
||||||
|
/// insufficient. This property is typically set to an instance of [AuthServer].
|
||||||
|
///
|
||||||
|
/// The validator is crucial for determining whether a request should be allowed
|
||||||
|
/// to proceed based on the provided authorization information.
|
||||||
final AuthValidator? validator;
|
final AuthValidator? validator;
|
||||||
|
|
||||||
/// The list of required scopes.
|
/// The list of required scopes for authorization.
|
||||||
///
|
///
|
||||||
/// If [validator] grants scope-limited authorizations (e.g., OAuth2 bearer tokens), the authorization
|
/// If [validator] grants scope-limited authorizations (e.g., OAuth2 bearer tokens), the authorization
|
||||||
/// provided by the request's header must have access to all [scopes] in order to move on to the next controller.
|
/// provided by the request's header must have access to all [scopes] in order to move on to the next controller.
|
||||||
|
@ -79,7 +102,7 @@ class Authorizer extends Controller {
|
||||||
/// an [AuthScope] and added to this list.
|
/// an [AuthScope] and added to this list.
|
||||||
final List<AuthScope>? scopes;
|
final List<AuthScope>? scopes;
|
||||||
|
|
||||||
/// Parses the Authorization header.
|
/// Parses the Authorization header of incoming requests.
|
||||||
///
|
///
|
||||||
/// The parser determines how to interpret the data in the Authorization header. Concrete subclasses
|
/// The parser determines how to interpret the data in the Authorization header. Concrete subclasses
|
||||||
/// are [AuthorizationBasicParser] and [AuthorizationBearerParser].
|
/// are [AuthorizationBasicParser] and [AuthorizationBearerParser].
|
||||||
|
@ -87,6 +110,20 @@ class Authorizer extends Controller {
|
||||||
/// Once parsed, the parsed value is validated by [validator].
|
/// Once parsed, the parsed value is validated by [validator].
|
||||||
final AuthorizationParser parser;
|
final AuthorizationParser parser;
|
||||||
|
|
||||||
|
/// Handles the incoming request by validating its authorization.
|
||||||
|
///
|
||||||
|
/// This method performs the following steps:
|
||||||
|
/// 1. Extracts the Authorization header from the request.
|
||||||
|
/// 2. If the header is missing, returns an unauthorized response.
|
||||||
|
/// 3. Attempts to parse the authorization data using the configured parser.
|
||||||
|
/// 4. Validates the parsed data using the configured validator.
|
||||||
|
/// 5. If validation succeeds, adds the authorization to the request and proceeds.
|
||||||
|
/// 6. If validation fails due to insufficient scope, returns a forbidden response.
|
||||||
|
/// 7. For other validation failures, returns an unauthorized response.
|
||||||
|
/// 8. Handles parsing exceptions by returning appropriate error responses.
|
||||||
|
///
|
||||||
|
/// @param request The incoming HTTP request to be authorized.
|
||||||
|
/// @return A [Future] that resolves to either the authorized [Request] or an error [Response].
|
||||||
@override
|
@override
|
||||||
FutureOr<RequestOrResponse> handle(Request request) async {
|
FutureOr<RequestOrResponse> handle(Request request) async {
|
||||||
final authData = request.raw.headers.value(HttpHeaders.authorizationHeader);
|
final authData = request.raw.headers.value(HttpHeaders.authorizationHeader);
|
||||||
|
@ -121,6 +158,19 @@ class Authorizer extends Controller {
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates an appropriate HTTP response based on the type of AuthorizationParserException.
|
||||||
|
///
|
||||||
|
/// This method takes an [AuthorizationParserException] as input and returns
|
||||||
|
/// a [Response] object based on the exception's reason:
|
||||||
|
///
|
||||||
|
/// - For [AuthorizationParserExceptionReason.malformed], it returns a 400 Bad Request
|
||||||
|
/// response with a body indicating an invalid authorization header.
|
||||||
|
/// - For [AuthorizationParserExceptionReason.missing], it returns a 401 Unauthorized
|
||||||
|
/// response.
|
||||||
|
/// - For any other reason, it returns a 500 Server Error response.
|
||||||
|
///
|
||||||
|
/// @param e The AuthorizationParserException that occurred during parsing.
|
||||||
|
/// @return A Response object appropriate to the exception reason.
|
||||||
Response _responseFromParseException(AuthorizationParserException e) {
|
Response _responseFromParseException(AuthorizationParserException e) {
|
||||||
switch (e.reason) {
|
switch (e.reason) {
|
||||||
case AuthorizationParserExceptionReason.malformed:
|
case AuthorizationParserExceptionReason.malformed:
|
||||||
|
@ -134,6 +184,21 @@ class Authorizer extends Controller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds a response modifier to the request to handle scope requirements.
|
||||||
|
///
|
||||||
|
/// This method is called after successful authorization and adds a response
|
||||||
|
/// modifier to the request. The modifier's purpose is to enhance 403 (Forbidden)
|
||||||
|
/// responses that are due to insufficient scope.
|
||||||
|
///
|
||||||
|
/// If this [Authorizer] has required scopes and the response is a 403 with a body
|
||||||
|
/// containing a "scope" key, this modifier will add any of this [Authorizer]'s
|
||||||
|
/// required scopes that aren't already present in the response body's scope list.
|
||||||
|
///
|
||||||
|
/// This ensures that if a downstream controller returns a 403 due to insufficient
|
||||||
|
/// scope, the response includes all the scopes required by both this [Authorizer]
|
||||||
|
/// and the downstream controller.
|
||||||
|
///
|
||||||
|
/// @param request The [Request] object to which the modifier will be added.
|
||||||
void _addScopeRequirementModifier(Request request) {
|
void _addScopeRequirementModifier(Request request) {
|
||||||
// If a controller returns a 403 because of invalid scope,
|
// If a controller returns a 403 because of invalid scope,
|
||||||
// this Authorizer adds its required scope as well.
|
// this Authorizer adds its required scope as well.
|
||||||
|
@ -154,10 +219,49 @@ class Authorizer extends Controller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Documents the components for the API documentation.
|
||||||
|
///
|
||||||
|
/// This method is responsible for registering custom API responses that are specific
|
||||||
|
/// to authorization-related errors. It adds three responses to the API documentation:
|
||||||
|
///
|
||||||
|
/// 1. "InsufficientScope": Used when the provided credentials or bearer token have
|
||||||
|
/// insufficient permissions to access a route.
|
||||||
|
///
|
||||||
|
/// 2. "InsufficientAccess": Used when the provided credentials or bearer token are
|
||||||
|
/// not authorized for a specific request.
|
||||||
|
///
|
||||||
|
/// 3. "MalformedAuthorizationHeader": Used when the provided Authorization header
|
||||||
|
/// is malformed.
|
||||||
|
///
|
||||||
|
/// Each response is registered with a description and a schema defining the
|
||||||
|
/// structure of the JSON response body.
|
||||||
|
///
|
||||||
|
/// @param context The APIDocumentContext used to register the responses.
|
||||||
@override
|
@override
|
||||||
void documentComponents(APIDocumentContext context) {
|
void documentComponents(APIDocumentContext context) {
|
||||||
|
/// Calls the superclass's documentComponents method.
|
||||||
|
///
|
||||||
|
/// This method invokes the documentComponents method of the superclass,
|
||||||
|
/// ensuring that any component documentation defined in the parent class
|
||||||
|
/// is properly registered in the API documentation context.
|
||||||
|
///
|
||||||
|
/// @param context The APIDocumentContext used for registering API components.
|
||||||
super.documentComponents(context);
|
super.documentComponents(context);
|
||||||
|
|
||||||
|
/// Registers an "InsufficientScope" response in the API documentation.
|
||||||
|
///
|
||||||
|
/// This response is used when the provided credentials or bearer token
|
||||||
|
/// have insufficient permissions to access a specific route. It includes
|
||||||
|
/// details about the error and the required scope for the operation.
|
||||||
|
///
|
||||||
|
/// The response has the following structure:
|
||||||
|
/// - A description explaining the insufficient scope error.
|
||||||
|
/// - Content of type "application/json" with a schema containing:
|
||||||
|
/// - An "error" field of type string.
|
||||||
|
/// - A "scope" field of type string, describing the required scope.
|
||||||
|
///
|
||||||
|
/// This response can be referenced in API operations to standardize
|
||||||
|
/// the documentation of insufficient scope errors.
|
||||||
context.responses.register(
|
context.responses.register(
|
||||||
"InsufficientScope",
|
"InsufficientScope",
|
||||||
APIResponse(
|
APIResponse(
|
||||||
|
@ -174,6 +278,19 @@ class Authorizer extends Controller {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Registers an "InsufficientAccess" response in the API documentation.
|
||||||
|
///
|
||||||
|
/// This response is used when the provided credentials or bearer token
|
||||||
|
/// are not authorized for a specific request. It includes details about
|
||||||
|
/// the error in a JSON format.
|
||||||
|
///
|
||||||
|
/// The response has the following structure:
|
||||||
|
/// - A description explaining the insufficient access error.
|
||||||
|
/// - Content of type "application/json" with a schema containing:
|
||||||
|
/// - An "error" field of type string.
|
||||||
|
///
|
||||||
|
/// This response can be referenced in API operations to standardize
|
||||||
|
/// the documentation of insufficient access errors.
|
||||||
context.responses.register(
|
context.responses.register(
|
||||||
"InsufficientAccess",
|
"InsufficientAccess",
|
||||||
APIResponse(
|
APIResponse(
|
||||||
|
@ -188,6 +305,18 @@ class Authorizer extends Controller {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Registers a "MalformedAuthorizationHeader" response in the API documentation.
|
||||||
|
///
|
||||||
|
/// This response is used when the provided Authorization header is malformed.
|
||||||
|
/// It includes details about the error in a JSON format.
|
||||||
|
///
|
||||||
|
/// The response has the following structure:
|
||||||
|
/// - A description explaining the malformed authorization header error.
|
||||||
|
/// - Content of type "application/json" with a schema containing:
|
||||||
|
/// - An "error" field of type string.
|
||||||
|
///
|
||||||
|
/// This response can be referenced in API operations to standardize
|
||||||
|
/// the documentation of malformed authorization header errors.
|
||||||
context.responses.register(
|
context.responses.register(
|
||||||
"MalformedAuthorizationHeader",
|
"MalformedAuthorizationHeader",
|
||||||
APIResponse(
|
APIResponse(
|
||||||
|
@ -203,6 +332,23 @@ class Authorizer extends Controller {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Documents the operations for the API documentation.
|
||||||
|
///
|
||||||
|
/// This method is responsible for adding security-related responses and requirements
|
||||||
|
/// to each operation in the API documentation. It performs the following tasks:
|
||||||
|
///
|
||||||
|
/// 1. Calls the superclass's documentOperations method to get the base operations.
|
||||||
|
/// 2. For each operation:
|
||||||
|
/// - Adds a 400 response for malformed authorization headers.
|
||||||
|
/// - Adds a 401 response for insufficient access.
|
||||||
|
/// - Adds a 403 response for insufficient scope.
|
||||||
|
/// - Retrieves security requirements from the validator.
|
||||||
|
/// - Adds these security requirements to the operation.
|
||||||
|
///
|
||||||
|
/// @param context The APIDocumentContext used for documenting the API.
|
||||||
|
/// @param route The route string for which operations are being documented.
|
||||||
|
/// @param path The APIPath object representing the path of the operations.
|
||||||
|
/// @return A map of operation names to APIOperation objects with added security documentation.
|
||||||
@override
|
@override
|
||||||
Map<String, APIOperation> documentOperations(
|
Map<String, APIOperation> documentOperations(
|
||||||
APIDocumentContext context,
|
APIDocumentContext context,
|
||||||
|
|
|
@ -1,11 +1,58 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
import 'package:protevus_auth/auth.dart';
|
import 'package:protevus_auth/auth.dart';
|
||||||
|
|
||||||
/// An exception thrown by [AuthServer].
|
/// An exception class for handling authentication server errors.
|
||||||
|
///
|
||||||
|
/// This class implements the [Exception] interface and is used to represent
|
||||||
|
/// various errors that can occur during the authentication process.
|
||||||
|
///
|
||||||
|
/// The [AuthServerException] contains:
|
||||||
|
/// - [reason]: An [AuthRequestError] enum value representing the specific error.
|
||||||
|
/// - [client]: An optional [AuthClient] associated with the error.
|
||||||
|
///
|
||||||
|
/// It also provides utility methods:
|
||||||
|
/// - [errorString]: A static method that converts [AuthRequestError] enum values to standardized error strings.
|
||||||
|
/// - [reasonString]: A getter that returns the error string for the current [reason].
|
||||||
|
///
|
||||||
|
/// The [toString] method is overridden to provide a custom string representation of the exception.
|
||||||
class AuthServerException implements Exception {
|
class AuthServerException implements Exception {
|
||||||
|
/// Creates an [AuthServerException] with the specified [reason] and optional [client].
|
||||||
|
///
|
||||||
|
/// The [reason] parameter is an [AuthRequestError] enum value representing the specific error.
|
||||||
|
/// The [client] parameter is an optional [AuthClient] associated with the error.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// var exception = AuthServerException(AuthRequestError.invalidRequest, null);
|
||||||
|
/// ```
|
||||||
AuthServerException(this.reason, this.client);
|
AuthServerException(this.reason, this.client);
|
||||||
|
|
||||||
/// Returns a string suitable to be included in a query string or JSON response body
|
/// Converts an [AuthRequestError] enum value to its corresponding string representation.
|
||||||
/// to indicate the error during processing an OAuth 2.0 request.
|
///
|
||||||
|
/// This static method takes an [AuthRequestError] as input and returns a standardized
|
||||||
|
/// string that represents the error. These strings are suitable for inclusion in
|
||||||
|
/// query strings or JSON response bodies when indicating errors during the processing
|
||||||
|
/// of OAuth 2.0 requests.
|
||||||
|
///
|
||||||
|
/// The returned strings conform to the error codes defined in the OAuth 2.0 specification,
|
||||||
|
/// with the exception of 'invalid_token', which is a custom addition.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// var errorString = AuthServerException.errorString(AuthRequestError.invalidRequest);
|
||||||
|
/// print(errorString); // Outputs: "invalid_request"
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// @param error The [AuthRequestError] enum value to convert.
|
||||||
|
/// @return A string representation of the error.
|
||||||
static String errorString(AuthRequestError error) {
|
static String errorString(AuthRequestError error) {
|
||||||
switch (error) {
|
switch (error) {
|
||||||
case AuthRequestError.invalidRequest:
|
case AuthRequestError.invalidRequest:
|
||||||
|
@ -36,25 +83,54 @@ class AuthServerException implements Exception {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The specific reason for the authentication error.
|
||||||
|
///
|
||||||
|
/// This property holds an [AuthRequestError] enum value that represents
|
||||||
|
/// the specific error that occurred during the authentication process.
|
||||||
|
/// It provides detailed information about why the authentication request failed.
|
||||||
AuthRequestError reason;
|
AuthRequestError reason;
|
||||||
|
|
||||||
|
/// The optional [AuthClient] associated with this exception.
|
||||||
|
///
|
||||||
|
/// This property may contain an [AuthClient] instance that is related to the
|
||||||
|
/// authentication error. It can be null if no specific client is associated
|
||||||
|
/// with the error or if the error occurred before client authentication.
|
||||||
|
///
|
||||||
|
/// This information can be useful for debugging or logging purposes, providing
|
||||||
|
/// context about which client encountered the authentication error.
|
||||||
AuthClient? client;
|
AuthClient? client;
|
||||||
|
|
||||||
|
/// Returns a string representation of the [reason] for this exception.
|
||||||
|
///
|
||||||
|
/// This getter utilizes the static [errorString] method to convert the
|
||||||
|
/// [AuthRequestError] enum value stored in [reason] to its corresponding
|
||||||
|
/// string representation.
|
||||||
|
///
|
||||||
|
/// @return A standardized string representation of the error reason.
|
||||||
String get reasonString {
|
String get reasonString {
|
||||||
return errorString(reason);
|
return errorString(reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a string representation of the [AuthServerException].
|
||||||
|
///
|
||||||
|
/// This method overrides the default [Object.toString] method to provide
|
||||||
|
/// a custom string representation of the exception. The returned string
|
||||||
|
/// includes the exception class name, the [reason] for the exception,
|
||||||
|
/// and the associated [client] (if any).
|
||||||
|
///
|
||||||
|
/// @return A string in the format "AuthServerException: [reason] [client]".
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return "AuthServerException: $reason $client";
|
return "AuthServerException: $reason $client";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The possible errors as defined by the OAuth 2.0 specification.
|
/// Enum representing possible errors as defined by the OAuth 2.0 specification.
|
||||||
///
|
///
|
||||||
/// Auth endpoints will use this list of values to determine the response sent back
|
/// Auth endpoints will use this list of values to determine the response sent back
|
||||||
/// to a client upon a failed request.
|
/// to a client upon a failed request.
|
||||||
enum AuthRequestError {
|
enum AuthRequestError {
|
||||||
/// The request was invalid...
|
/// Represents an invalid request error.
|
||||||
///
|
///
|
||||||
/// The request is missing a required parameter, includes an
|
/// The request is missing a required parameter, includes an
|
||||||
/// unsupported parameter value (other than grant type),
|
/// unsupported parameter value (other than grant type),
|
||||||
|
@ -63,7 +139,7 @@ enum AuthRequestError {
|
||||||
/// client, or is otherwise malformed.
|
/// client, or is otherwise malformed.
|
||||||
invalidRequest,
|
invalidRequest,
|
||||||
|
|
||||||
/// The client was invalid...
|
/// Represents an invalid client error.
|
||||||
///
|
///
|
||||||
/// Client authentication failed (e.g., unknown client, no
|
/// Client authentication failed (e.g., unknown client, no
|
||||||
/// client authentication included, or unsupported
|
/// client authentication included, or unsupported
|
||||||
|
@ -77,7 +153,7 @@ enum AuthRequestError {
|
||||||
/// matching the authentication scheme used by the client.
|
/// matching the authentication scheme used by the client.
|
||||||
invalidClient,
|
invalidClient,
|
||||||
|
|
||||||
/// The grant was invalid...
|
/// Represents an invalid grant error.
|
||||||
///
|
///
|
||||||
/// The provided authorization grant (e.g., authorization
|
/// The provided authorization grant (e.g., authorization
|
||||||
/// code, resource owner credentials) or refresh token is
|
/// code, resource owner credentials) or refresh token is
|
||||||
|
@ -86,36 +162,81 @@ enum AuthRequestError {
|
||||||
/// another client.
|
/// another client.
|
||||||
invalidGrant,
|
invalidGrant,
|
||||||
|
|
||||||
/// The requested scope is invalid, unknown, malformed, or exceeds the scope granted by the resource owner.
|
/// Represents an invalid scope error.
|
||||||
///
|
///
|
||||||
|
/// This error occurs when the requested scope is invalid, unknown, malformed,
|
||||||
|
/// or exceeds the scope granted by the resource owner. It typically indicates
|
||||||
|
/// that the client has requested access to resources or permissions that are
|
||||||
|
/// either not recognized by the authorization server or not authorized for
|
||||||
|
/// the particular client or user.
|
||||||
|
///
|
||||||
|
/// In the OAuth 2.0 flow, this error might be returned if a client requests
|
||||||
|
/// access to a scope that doesn't exist or that the user hasn't granted
|
||||||
|
/// permission for.
|
||||||
invalidScope,
|
invalidScope,
|
||||||
|
|
||||||
/// The authorization grant type is not supported by the authorization server.
|
/// Represents an unsupported grant type error.
|
||||||
///
|
///
|
||||||
|
/// This error occurs when the authorization server does not support the
|
||||||
|
/// grant type requested by the client. It typically indicates that the
|
||||||
|
/// client has specified a grant type that is either not recognized or
|
||||||
|
/// not implemented by the authorization server.
|
||||||
|
///
|
||||||
|
/// In the OAuth 2.0 flow, this error might be returned if, for example,
|
||||||
|
/// a client requests a grant type like "password" when the server only
|
||||||
|
/// supports "authorization_code" and "refresh_token" grant types.
|
||||||
unsupportedGrantType,
|
unsupportedGrantType,
|
||||||
|
|
||||||
/// The authorization server does not support obtaining an authorization code using this method.
|
/// Represents an unsupported response type error.
|
||||||
///
|
///
|
||||||
|
/// This error occurs when the authorization server does not support obtaining
|
||||||
|
/// an authorization code using the specified response type. It typically
|
||||||
|
/// indicates that the client has requested a response type that is not
|
||||||
|
/// recognized or not implemented by the authorization server.
|
||||||
unsupportedResponseType,
|
unsupportedResponseType,
|
||||||
|
|
||||||
/// The authenticated client is not authorized to use this authorization grant type.
|
/// Represents an unauthorized client error.
|
||||||
///
|
///
|
||||||
|
/// This error occurs when the client is not authorized to request an
|
||||||
|
/// authorization code using this method. It typically indicates that
|
||||||
|
/// the client does not have the necessary permissions or credentials
|
||||||
|
/// to perform the requested action, even though it may be properly
|
||||||
|
/// authenticated.
|
||||||
unauthorizedClient,
|
unauthorizedClient,
|
||||||
|
|
||||||
/// The resource owner or authorization server denied the request.
|
/// Represents an access denied error.
|
||||||
///
|
///
|
||||||
|
/// This error occurs when the resource owner or authorization server denies the request.
|
||||||
|
/// It is typically used when the authenticated user does not have sufficient permissions
|
||||||
|
/// to perform the requested action, or when the user explicitly denies authorization
|
||||||
|
/// during the OAuth flow.
|
||||||
accessDenied,
|
accessDenied,
|
||||||
|
|
||||||
/// The server encountered an error during processing the request.
|
/// Represents a server error.
|
||||||
///
|
///
|
||||||
|
/// This error occurs when the authorization server encounters an unexpected
|
||||||
|
/// condition that prevented it from fulfilling the request. This is typically
|
||||||
|
/// used for internal server errors or other unexpected issues that prevent
|
||||||
|
/// the server from properly processing the authentication request.
|
||||||
serverError,
|
serverError,
|
||||||
|
|
||||||
/// The server is temporarily unable to fulfill the request.
|
/// Represents a temporarily unavailable error.
|
||||||
///
|
///
|
||||||
|
/// This error occurs when the authorization server is temporarily unable to handle
|
||||||
|
/// the request due to a temporary overloading or maintenance of the server.
|
||||||
|
/// The client may repeat the request at a later time. The server SHOULD include
|
||||||
|
/// a Retry-After HTTP header field in the response indicating how long the client
|
||||||
|
/// should wait before retrying the request.
|
||||||
temporarilyUnavailable,
|
temporarilyUnavailable,
|
||||||
|
|
||||||
/// Indicates that the token is invalid.
|
/// Represents an invalid token error.
|
||||||
///
|
///
|
||||||
/// This particular error reason is not part of the OAuth 2.0 spec.
|
/// This error occurs when the provided token is invalid, expired, or otherwise
|
||||||
|
/// not acceptable for the requested operation. It is typically used when a client
|
||||||
|
/// presents an access token that cannot be validated or is no longer valid.
|
||||||
|
///
|
||||||
|
/// Note: This particular error reason is not part of the standard OAuth 2.0
|
||||||
|
/// specification. It is a custom addition to handle scenarios specific to
|
||||||
|
/// token validation that are not covered by other standard error types.
|
||||||
invalidToken
|
invalidToken
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,32 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
import 'package:protevus_auth/auth.dart';
|
import 'package:protevus_auth/auth.dart';
|
||||||
import 'package:protevus_http/http.dart';
|
import 'package:protevus_http/http.dart';
|
||||||
|
|
||||||
/// Represents an OAuth 2.0 client ID and secret pair.
|
/// Represents an OAuth 2.0 client ID and secret pair.
|
||||||
///
|
///
|
||||||
/// See the conduit/managed_auth library for a concrete implementation of this type.
|
/// This class encapsulates the information necessary for OAuth 2.0 client authentication.
|
||||||
|
/// It can represent both public and confidential clients, with support for the authorization code grant flow.
|
||||||
///
|
///
|
||||||
/// Use the command line tool `conduit auth` to create instances of this type and store them to a database.
|
/// Use the command line tool `conduit auth` to create instances of this type and store them to a database.
|
||||||
class AuthClient {
|
class AuthClient {
|
||||||
/// Creates an instance of [AuthClient].
|
/// Creates an instance of [AuthClient].
|
||||||
///
|
///
|
||||||
/// [id] must not be null. [hashedSecret] and [salt] must either both be null or both be valid values. If [hashedSecret] and [salt]
|
/// This constructor creates an [AuthClient] with the given parameters.
|
||||||
/// are valid values, this client is a confidential client. Otherwise, the client is public. The terms 'confidential' and 'public'
|
|
||||||
/// are described by the OAuth 2.0 specification.
|
|
||||||
///
|
///
|
||||||
/// If this client supports scopes, [allowedScopes] must contain a list of scopes that tokens may request when authorized
|
/// If this client supports scopes, [allowedScopes] must contain a list of scopes that tokens may request when authorized
|
||||||
/// by this client.
|
/// by this client.
|
||||||
|
///
|
||||||
|
/// NOTE: [id] must not be null. [hashedSecret] and [salt] must either both be null or both be valid values. If [hashedSecret] and [salt]
|
||||||
|
/// are valid values, this client is a confidential client. Otherwise, the client is public. The terms 'confidential' and 'public'
|
||||||
|
/// are described by the OAuth 2.0 specification.
|
||||||
AuthClient(
|
AuthClient(
|
||||||
String id,
|
String id,
|
||||||
String? hashedSecret,
|
String? hashedSecret,
|
||||||
|
@ -29,6 +41,16 @@ class AuthClient {
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Creates an instance of a public [AuthClient].
|
/// Creates an instance of a public [AuthClient].
|
||||||
|
///
|
||||||
|
/// This constructor creates a public [AuthClient] with the given [id].
|
||||||
|
/// Public clients do not have a client secret.
|
||||||
|
///
|
||||||
|
/// - [id]: The unique identifier for the client.
|
||||||
|
/// - [allowedScopes]: Optional list of scopes that this client is allowed to request.
|
||||||
|
/// - [redirectURI]: Optional URI to redirect to after authorization.
|
||||||
|
///
|
||||||
|
/// This is equivalent to calling [AuthClient.withRedirectURI] with null values for
|
||||||
|
/// hashedSecret and salt.
|
||||||
AuthClient.public(String id,
|
AuthClient.public(String id,
|
||||||
{List<AuthScope>? allowedScopes, String? redirectURI})
|
{List<AuthScope>? allowedScopes, String? redirectURI})
|
||||||
: this.withRedirectURI(
|
: this.withRedirectURI(
|
||||||
|
@ -41,7 +63,17 @@ class AuthClient {
|
||||||
|
|
||||||
/// Creates an instance of [AuthClient] that uses the authorization code grant flow.
|
/// Creates an instance of [AuthClient] that uses the authorization code grant flow.
|
||||||
///
|
///
|
||||||
/// All values must be non-null. This is confidential client.
|
/// This constructor creates a confidential [AuthClient] with the given parameters.
|
||||||
|
///
|
||||||
|
/// - [id]: The unique identifier for the client.
|
||||||
|
/// - [hashedSecret]: The hashed secret of the client.
|
||||||
|
/// - [salt]: The salt used to hash the client secret.
|
||||||
|
/// - [redirectURI]: The URI to redirect to after authorization.
|
||||||
|
/// - [allowedScopes]: Optional list of scopes that this client is allowed to request.
|
||||||
|
///
|
||||||
|
/// This constructor is specifically for clients that use the authorization code grant flow,
|
||||||
|
/// which requires a redirect URI. All parameters except [allowedScopes] must be non-null.
|
||||||
|
/// The presence of [hashedSecret] and [salt] indicates that this is a confidential client.
|
||||||
AuthClient.withRedirectURI(
|
AuthClient.withRedirectURI(
|
||||||
this.id,
|
this.id,
|
||||||
this.hashedSecret,
|
this.hashedSecret,
|
||||||
|
@ -52,28 +84,57 @@ class AuthClient {
|
||||||
this.allowedScopes = allowedScopes;
|
this.allowedScopes = allowedScopes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The list of allowed scopes for this client.
|
||||||
|
///
|
||||||
|
/// This private variable stores the allowed scopes for the AuthClient.
|
||||||
|
/// It is used internally to manage and validate the scopes that this client
|
||||||
|
/// is authorized to request during the authentication process.
|
||||||
List<AuthScope>? _allowedScopes;
|
List<AuthScope>? _allowedScopes;
|
||||||
|
|
||||||
/// The ID of the client.
|
/// The unique identifier for this OAuth 2.0 client.
|
||||||
|
///
|
||||||
|
/// This is a required field for all OAuth 2.0 clients and is used to identify
|
||||||
|
/// the client during the authentication and authorization process. It should
|
||||||
|
/// be a string value that is unique among all clients registered with the
|
||||||
|
/// authorization server.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
/// The hashed secret of the client.
|
/// The hashed secret of the client.
|
||||||
///
|
///
|
||||||
/// This value may be null if the client is public. See [isPublic].
|
/// This property stores the hashed version of the client's secret, which is used for authentication
|
||||||
|
/// in confidential clients. The secret is hashed for security reasons, to avoid storing the raw secret.
|
||||||
|
///
|
||||||
|
/// This value may be null if the client is public. A null value indicates that this is a public client,
|
||||||
|
/// which doesn't use a client secret for authentication. See [isPublic] for more information on
|
||||||
|
/// determining if a client is public or confidential.
|
||||||
|
///
|
||||||
|
/// The hashed secret is typically used in conjunction with the [salt] property to verify
|
||||||
|
/// the client's credentials during the authentication process.
|
||||||
String? hashedSecret;
|
String? hashedSecret;
|
||||||
|
|
||||||
/// The salt [hashedSecret] was hashed with.
|
/// The salt used to hash the client secret.
|
||||||
///
|
///
|
||||||
/// This value may be null if the client is public. See [isPublic].
|
/// This value may be null if the client is public. See [isPublic].
|
||||||
String? salt;
|
String? salt;
|
||||||
|
|
||||||
/// The redirection URI for authorization codes and/or tokens.
|
/// The redirection URI for authorization codes and/or tokens.
|
||||||
///
|
///
|
||||||
|
/// This property stores the URI where the authorization server should redirect
|
||||||
|
/// the user after they grant or deny permission to the client. It is used in
|
||||||
|
/// the authorization code grant flow of OAuth 2.0.
|
||||||
|
///
|
||||||
|
/// In the context of OAuth 2.0:
|
||||||
|
/// - For authorization code grant, this URI is where the authorization code is sent.
|
||||||
|
/// - For implicit grant, this URI is where the access token is sent.
|
||||||
|
///
|
||||||
/// This value may be null if the client doesn't support the authorization code flow.
|
/// This value may be null if the client doesn't support the authorization code flow.
|
||||||
String? redirectURI;
|
String? redirectURI;
|
||||||
|
|
||||||
/// The list of scopes available when authorizing with this client.
|
/// The list of scopes available when authorizing with this client.
|
||||||
///
|
///
|
||||||
|
/// This getter returns the list of allowed scopes for the client. The setter
|
||||||
|
/// filters the provided list to remove any redundant scopes.
|
||||||
|
///
|
||||||
/// Scoping is determined by this instance; i.e. the authorizing client determines which scopes a token
|
/// Scoping is determined by this instance; i.e. the authorizing client determines which scopes a token
|
||||||
/// has. This list contains all valid scopes for this client. If null, client does not support scopes
|
/// has. This list contains all valid scopes for this client. If null, client does not support scopes
|
||||||
/// and all access tokens have same authorization.
|
/// and all access tokens have same authorization.
|
||||||
|
@ -87,32 +148,54 @@ class AuthClient {
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether or not this instance allows scoping or not.
|
/// Determines if this instance supports authorization scopes.
|
||||||
///
|
///
|
||||||
/// In application's that do not use authorization scopes, this will return false.
|
/// In application's that do not use authorization scopes, this will return false.
|
||||||
/// Otherwise, will return true.
|
/// Otherwise, will return true.
|
||||||
bool get supportsScopes => allowedScopes != null;
|
bool get supportsScopes => allowedScopes != null;
|
||||||
|
|
||||||
/// Whether or not this client can issue tokens for the provided [scope].
|
/// Determines if this client can issue tokens for the provided [scope].
|
||||||
|
///
|
||||||
|
/// This method checks if the given [scope] is allowed for this client by comparing it
|
||||||
|
/// against the client's [allowedScopes]. It returns true if the provided [scope] is
|
||||||
|
/// a subset of or equal to any of the scopes in [allowedScopes].
|
||||||
|
///
|
||||||
|
/// If [allowedScopes] is null or empty, this method returns false, indicating that
|
||||||
|
/// no scopes are allowed for this client.
|
||||||
|
///
|
||||||
|
/// [scope]: The AuthScope to check against this client's allowed scopes.
|
||||||
|
///
|
||||||
|
/// Returns true if the scope is allowed, false otherwise.
|
||||||
bool allowsScope(AuthScope scope) {
|
bool allowsScope(AuthScope scope) {
|
||||||
return allowedScopes
|
return allowedScopes
|
||||||
?.any((clientScope) => scope.isSubsetOrEqualTo(clientScope)) ??
|
?.any((clientScope) => scope.isSubsetOrEqualTo(clientScope)) ??
|
||||||
false;
|
false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether or not this is a public or confidential client.
|
/// Whether or not this is a public client.
|
||||||
///
|
///
|
||||||
/// Public clients do not have a client secret and are used for clients that can't store
|
/// Public clients do not have a client secret and are used for clients that can't store
|
||||||
/// their secret confidentially, i.e. JavaScript browser applications.
|
/// their secret confidentially, i.e. JavaScript browser applications.
|
||||||
bool get isPublic => hashedSecret == null;
|
bool get isPublic => hashedSecret == null;
|
||||||
|
|
||||||
/// Whether or not this is a public or confidential client.
|
/// Determines whether this client is confidential or public.
|
||||||
///
|
///
|
||||||
/// Confidential clients have a client secret that must be used when authenticating with
|
/// Confidential clients have a client secret that must be used when authenticating with
|
||||||
/// a client-authenticated request. Confidential clients are used when you can
|
/// a client-authenticated request. Confidential clients are used when you can
|
||||||
/// be sure that the client secret cannot be viewed by anyone outside of the developer.
|
/// be sure that the client secret cannot be viewed by anyone outside of the developer.
|
||||||
bool get isConfidential => hashedSecret != null;
|
bool get isConfidential => hashedSecret != null;
|
||||||
|
|
||||||
|
/// Returns a string representation of the AuthClient instance.
|
||||||
|
///
|
||||||
|
/// This method provides a human-readable description of the AuthClient, including:
|
||||||
|
/// - Whether the client is public or confidential
|
||||||
|
/// - The client's ID
|
||||||
|
/// - The client's redirect URI (if set)
|
||||||
|
///
|
||||||
|
/// The format of the returned string is:
|
||||||
|
/// "AuthClient (public/confidential): [client_id] [redirect_uri]"
|
||||||
|
///
|
||||||
|
/// @return A string representation of the AuthClient.
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return "AuthClient (${isPublic ? "public" : "confidental"}): $id $redirectURI";
|
return "AuthClient (${isPublic ? "public" : "confidental"}): $id $redirectURI";
|
||||||
|
@ -121,24 +204,67 @@ class AuthClient {
|
||||||
|
|
||||||
/// Represents an OAuth 2.0 token.
|
/// Represents an OAuth 2.0 token.
|
||||||
///
|
///
|
||||||
/// [AuthServerDelegate] and [AuthServer] will exchange OAuth 2.0
|
/// This class encapsulates the properties and functionality of an OAuth 2.0 token,
|
||||||
/// tokens through instances of this type.
|
/// including access token, refresh token, expiration details, and associated scopes.
|
||||||
|
/// It is used by [AuthServerDelegate] and [AuthServer] to exchange OAuth 2.0 tokens.
|
||||||
///
|
///
|
||||||
/// See the `package:conduit_core/managed_auth` library for a concrete implementation of this type.
|
/// See the `package:conduit_core/managed_auth` library for a concrete implementation of this type.
|
||||||
class AuthToken {
|
class AuthToken {
|
||||||
/// The value to be passed as a Bearer Authorization header.
|
/// The access token string for OAuth 2.0 authentication.
|
||||||
|
///
|
||||||
|
/// This token is used in the Authorization header of HTTP requests to authenticate
|
||||||
|
/// the client. It should be included in the header as "Bearer <accessToken>".
|
||||||
|
///
|
||||||
|
/// The access token is typically a short-lived credential that grants access to
|
||||||
|
/// protected resources on behalf of the resource owner (user).
|
||||||
|
///
|
||||||
|
/// This value may be null if the token has not been issued or has been invalidated.
|
||||||
String? accessToken;
|
String? accessToken;
|
||||||
|
|
||||||
/// The value to be passed for refreshing a token.
|
/// The refresh token associated with this OAuth 2.0 token.
|
||||||
|
///
|
||||||
|
/// A refresh token is a credential that can be used to obtain a new access token
|
||||||
|
/// when the current access token becomes invalid or expires. This allows the client
|
||||||
|
/// to obtain continued access to protected resources without requiring the resource
|
||||||
|
/// owner to re-authorize the application.
|
||||||
|
///
|
||||||
|
/// This value may be null if the authorization server does not issue refresh tokens
|
||||||
|
/// or if the token has not been issued with a refresh token.
|
||||||
String? refreshToken;
|
String? refreshToken;
|
||||||
|
|
||||||
/// The time this token was issued on.
|
/// The time this token was issued on.
|
||||||
|
///
|
||||||
|
/// This property represents the date and time when the OAuth 2.0 token was originally issued.
|
||||||
|
/// It can be used to calculate the age of the token or to implement token refresh policies.
|
||||||
|
/// The value is stored as a [DateTime] object, which allows for easy manipulation and comparison.
|
||||||
|
///
|
||||||
|
/// This value may be null if the issue date is not tracked or has not been set.
|
||||||
DateTime? issueDate;
|
DateTime? issueDate;
|
||||||
|
|
||||||
/// The time when this token expires.
|
/// The expiration date and time of this token.
|
||||||
|
///
|
||||||
|
/// This property represents the point in time when the OAuth 2.0 token will become invalid.
|
||||||
|
/// After this date and time, the token should no longer be accepted for authentication.
|
||||||
|
///
|
||||||
|
/// The value is stored as a [DateTime] object, which allows for easy comparison with the current time
|
||||||
|
/// to determine if the token has expired. This is typically used in conjunction with [issueDate]
|
||||||
|
/// to calculate the token's lifespan and manage token refresh cycles.
|
||||||
|
///
|
||||||
|
/// This value may be null if the token does not have an expiration date or if it has not been set.
|
||||||
DateTime? expirationDate;
|
DateTime? expirationDate;
|
||||||
|
|
||||||
/// The type of token, currently only 'bearer' is valid.
|
/// The type of token used for authentication.
|
||||||
|
///
|
||||||
|
/// This property specifies the type of token being used. In the OAuth 2.0 framework,
|
||||||
|
/// the most common token type is 'bearer'. The token type is typically used in the
|
||||||
|
/// HTTP Authorization header to indicate how the access token should be used.
|
||||||
|
///
|
||||||
|
/// Currently, only 'bearer' is considered valid for this implementation.
|
||||||
|
///
|
||||||
|
/// Example usage in an HTTP header:
|
||||||
|
/// Authorization: Bearer <access_token>
|
||||||
|
///
|
||||||
|
/// This value may be null if the token type has not been set or is unknown.
|
||||||
String? type;
|
String? type;
|
||||||
|
|
||||||
/// The identifier of the resource owner.
|
/// The identifier of the resource owner.
|
||||||
|
@ -146,15 +272,60 @@ class AuthToken {
|
||||||
/// Tokens are owned by a resource owner, typically a User, Profile or Account
|
/// Tokens are owned by a resource owner, typically a User, Profile or Account
|
||||||
/// in an application. This value is the primary key or identifying value of those
|
/// in an application. This value is the primary key or identifying value of those
|
||||||
/// instances.
|
/// instances.
|
||||||
|
///
|
||||||
|
/// This property represents the unique identifier of the resource owner associated
|
||||||
|
/// with the OAuth 2.0 token. It is typically used to link the token to a specific
|
||||||
|
/// user or account in the system.
|
||||||
|
///
|
||||||
|
/// The value is stored as an integer, which could be:
|
||||||
|
/// - A database primary key
|
||||||
|
/// - A unique user ID
|
||||||
|
/// - Any other numeric identifier that uniquely identifies the resource owner
|
||||||
|
///
|
||||||
|
/// This property may be null if the token is not associated with a specific
|
||||||
|
/// resource owner or if the association has not been established.
|
||||||
int? resourceOwnerIdentifier;
|
int? resourceOwnerIdentifier;
|
||||||
|
|
||||||
/// The client ID this token was issued from.
|
/// The client ID associated with this token.
|
||||||
|
///
|
||||||
|
/// This property represents the unique identifier of the OAuth 2.0 client
|
||||||
|
/// that was used to obtain this token. It is used to link the token back
|
||||||
|
/// to the client application that requested it.
|
||||||
|
///
|
||||||
|
/// The client ID is typically assigned by the authorization server when
|
||||||
|
/// the client application is registered, and it's used to identify the
|
||||||
|
/// client during the authentication and token issuance process.
|
||||||
late String clientID;
|
late String clientID;
|
||||||
|
|
||||||
/// Scopes this token has access to.
|
/// The list of authorization scopes associated with this token.
|
||||||
|
///
|
||||||
|
/// This property represents the set of permissions or access rights granted to this token.
|
||||||
|
/// Each [AuthScope] in the list defines a specific area of access or functionality
|
||||||
|
/// that the token holder is allowed to use.
|
||||||
|
///
|
||||||
|
/// The scopes determine what actions or resources the token can access within the system.
|
||||||
|
/// If this list is null, it typically means the token has no specific scope restrictions
|
||||||
|
/// and may have full access (depending on the system's implementation).
|
||||||
|
///
|
||||||
|
/// This property is crucial for implementing fine-grained access control in OAuth 2.0
|
||||||
|
/// systems, allowing for precise definition of what each token is allowed to do.
|
||||||
List<AuthScope>? scopes;
|
List<AuthScope>? scopes;
|
||||||
|
|
||||||
/// Whether or not this token is expired by evaluated [expirationDate].
|
/// Determines whether this token has expired.
|
||||||
|
///
|
||||||
|
/// This getter compares the token's [expirationDate] with the current UTC time
|
||||||
|
/// to determine if the token has expired. It returns true if the token has
|
||||||
|
/// expired, and false if it is still valid.
|
||||||
|
///
|
||||||
|
/// The comparison is done by calculating the difference in seconds between
|
||||||
|
/// the expiration date and the current time. If this difference is less than
|
||||||
|
/// or equal to zero, the token is considered expired.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// [bool]: true if the token has expired, false otherwise.
|
||||||
|
///
|
||||||
|
/// Note: This getter assumes that [expirationDate] is not null. If it is null,
|
||||||
|
/// this will result in a null pointer exception.
|
||||||
bool get isExpired {
|
bool get isExpired {
|
||||||
return expirationDate!.difference(DateTime.now().toUtc()).inSeconds <= 0;
|
return expirationDate!.difference(DateTime.now().toUtc()).inSeconds <= 0;
|
||||||
}
|
}
|
||||||
|
@ -182,37 +353,118 @@ class AuthToken {
|
||||||
|
|
||||||
/// Represents an OAuth 2.0 authorization code.
|
/// Represents an OAuth 2.0 authorization code.
|
||||||
///
|
///
|
||||||
/// [AuthServerDelegate] and [AuthServer] will exchange OAuth 2.0
|
/// This class encapsulates the properties and functionality of an OAuth 2.0 authorization code,
|
||||||
/// authorization codes through instances of this type.
|
/// which is used in the authorization code grant flow. It contains information such as the code itself,
|
||||||
|
/// associated client and resource owner details, issue and expiration dates, and requested scopes.
|
||||||
///
|
///
|
||||||
/// See the conduit/managed_auth library for a concrete implementation of this type.
|
/// See the conduit/managed_auth library for a concrete implementation of this type.
|
||||||
class AuthCode {
|
class AuthCode {
|
||||||
/// The actual one-time code used to exchange for tokens.
|
/// The actual one-time code used to exchange for tokens.
|
||||||
|
///
|
||||||
|
/// This property represents the authorization code in the OAuth 2.0 authorization code flow.
|
||||||
|
/// It is a short-lived, single-use code that is issued by the authorization server and can be
|
||||||
|
/// exchanged for an access token and, optionally, a refresh token.
|
||||||
|
///
|
||||||
|
/// The code is typically valid for a short period (usually a few minutes) and can only be
|
||||||
|
/// used once. After it has been exchanged for tokens, it becomes invalid.
|
||||||
|
///
|
||||||
|
/// This value may be null if the code has not been generated yet or has been invalidated.
|
||||||
String? code;
|
String? code;
|
||||||
|
|
||||||
/// The client ID the authorization code was issued under.
|
/// The client ID associated with this authorization code.
|
||||||
|
///
|
||||||
|
/// This property represents the unique identifier of the OAuth 2.0 client
|
||||||
|
/// that requested the authorization code. It is used to link the authorization
|
||||||
|
/// code back to the client application that initiated the OAuth flow.
|
||||||
|
///
|
||||||
|
/// The client ID is typically assigned by the authorization server when
|
||||||
|
/// the client application is registered, and it's used to identify the
|
||||||
|
/// client during the authorization code exchange process.
|
||||||
|
///
|
||||||
|
/// This property is marked as 'late', indicating that it must be initialized
|
||||||
|
/// before it's accessed, but not necessarily in the constructor.
|
||||||
late String clientID;
|
late String clientID;
|
||||||
|
|
||||||
/// The identifier of the resource owner.
|
/// The identifier of the resource owner associated with this authorization code.
|
||||||
///
|
///
|
||||||
/// Authorization codes are owned by a resource owner, typically a User, Profile or Account
|
/// Authorization codes are owned by a resource owner, typically a User, Profile or Account
|
||||||
/// in an application. This value is the primary key or identifying value of those
|
/// in an application. This value is the primary key or identifying value of those
|
||||||
/// instances.
|
/// instances.
|
||||||
int? resourceOwnerIdentifier;
|
int? resourceOwnerIdentifier;
|
||||||
|
|
||||||
/// The timestamp this authorization code was issued on.
|
/// The timestamp when this authorization code was issued.
|
||||||
|
///
|
||||||
|
/// This property represents the date and time when the OAuth 2.0 authorization code
|
||||||
|
/// was originally created and issued by the authorization server. It can be used to:
|
||||||
|
/// - Calculate the age of the authorization code
|
||||||
|
/// - Implement expiration policies
|
||||||
|
/// - Audit the authorization process
|
||||||
|
///
|
||||||
|
/// The value is stored as a [DateTime] object, which allows for easy manipulation
|
||||||
|
/// and comparison with other dates and times.
|
||||||
|
///
|
||||||
|
/// This value may be null if the issue date is not tracked or has not been set.
|
||||||
DateTime? issueDate;
|
DateTime? issueDate;
|
||||||
|
|
||||||
/// When this authorization code expires, recommended for 10 minutes after issue date.
|
/// The expiration date and time of this authorization code.
|
||||||
|
///
|
||||||
|
/// This property represents the point in time when the OAuth 2.0 authorization code
|
||||||
|
/// will become invalid. After this date and time, the code should no longer be
|
||||||
|
/// accepted for token exchange.
|
||||||
|
///
|
||||||
|
/// It is recommended to set this value to 10 minutes after the [issueDate] to
|
||||||
|
/// limit the window of opportunity for potential attacks using intercepted
|
||||||
|
/// authorization codes.
|
||||||
|
///
|
||||||
|
/// The value is stored as a [DateTime] object, which allows for easy comparison
|
||||||
|
/// with the current time to determine if the code has expired. This is typically
|
||||||
|
/// used in conjunction with [issueDate] to enforce the short-lived nature of
|
||||||
|
/// authorization codes.
|
||||||
|
///
|
||||||
|
/// This value may be null if the authorization code does not have an expiration
|
||||||
|
/// date or if it has not been set.
|
||||||
DateTime? expirationDate;
|
DateTime? expirationDate;
|
||||||
|
|
||||||
/// Whether or not this authorization code has already been exchanged for a token.
|
/// Indicates whether this authorization code has already been exchanged for a token.
|
||||||
|
///
|
||||||
|
/// In the OAuth 2.0 authorization code flow, an authorization code should only be used once
|
||||||
|
/// to obtain an access token. This property helps track whether the code has been exchanged.
|
||||||
|
///
|
||||||
|
/// - If `true`, the code has already been used to obtain a token and should not be accepted again.
|
||||||
|
/// - If `false` or `null`, the code has not yet been exchanged and may still be valid for token issuance.
|
||||||
|
///
|
||||||
|
/// This property is crucial for preventing authorization code replay attacks, where an attacker
|
||||||
|
/// might attempt to use a single authorization code multiple times.
|
||||||
bool? hasBeenExchanged;
|
bool? hasBeenExchanged;
|
||||||
|
|
||||||
/// Scopes the exchanged token will have.
|
/// The list of scopes requested for the token to be exchanged.
|
||||||
|
///
|
||||||
|
/// This property represents the set of permissions or access rights that are being
|
||||||
|
/// requested for the OAuth 2.0 token during the authorization code exchange process.
|
||||||
|
/// Each [AuthScope] in the list defines a specific area of access or functionality
|
||||||
|
/// that the token is requesting to use.
|
||||||
|
///
|
||||||
|
/// If this list is null, it typically means no specific scopes are being requested,
|
||||||
|
/// and the token may receive default scopes or full access (depending on the system's
|
||||||
|
/// implementation and configuration).
|
||||||
|
///
|
||||||
|
/// The actual scopes granted to the token may be a subset of these requested scopes,
|
||||||
|
/// based on the authorization server's policies and the resource owner's consent.
|
||||||
List<AuthScope>? requestedScopes;
|
List<AuthScope>? requestedScopes;
|
||||||
|
|
||||||
/// Whether or not this code has expired yet, according to its [expirationDate].
|
/// Determines whether this authorization code has expired.
|
||||||
|
///
|
||||||
|
/// This getter compares the [expirationDate] of the authorization code with the current UTC time
|
||||||
|
/// to determine if the code has expired. It returns true if the code has expired, and false if it is still valid.
|
||||||
|
///
|
||||||
|
/// The comparison is done by calculating the difference in seconds between the expiration date and the current time.
|
||||||
|
/// If this difference is less than or equal to zero, the code is considered expired.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// [bool]: true if the authorization code has expired, false otherwise.
|
||||||
|
///
|
||||||
|
/// Note: This getter assumes that [expirationDate] is not null. If it is null,
|
||||||
|
/// this will result in a null pointer exception.
|
||||||
bool get isExpired {
|
bool get isExpired {
|
||||||
return expirationDate!.difference(DateTime.now().toUtc()).inSeconds <= 0;
|
return expirationDate!.difference(DateTime.now().toUtc()).inSeconds <= 0;
|
||||||
}
|
}
|
||||||
|
@ -220,12 +472,34 @@ class AuthCode {
|
||||||
|
|
||||||
/// Authorization information for a [Request] after it has passed through an [Authorizer].
|
/// Authorization information for a [Request] after it has passed through an [Authorizer].
|
||||||
///
|
///
|
||||||
|
/// This class encapsulates various pieces of authorization information, including:
|
||||||
|
/// - The client ID under which the permission was granted
|
||||||
|
/// - The identifier for the resource owner (if applicable)
|
||||||
|
/// - The [AuthValidator] that granted the permission
|
||||||
|
/// - Basic authorization credentials (if provided)
|
||||||
|
/// - A list of scopes that this authorization has access to
|
||||||
|
///
|
||||||
|
/// It also provides a method to check if the authorization has access to a specific scope.
|
||||||
|
///
|
||||||
|
/// This class is typically used in conjunction with [Authorizer] and [AuthValidator]
|
||||||
|
/// to manage and verify authorization in a request-response cycle.
|
||||||
/// After a request has passed through an [Authorizer], an instance of this type
|
/// After a request has passed through an [Authorizer], an instance of this type
|
||||||
/// is created and attached to the request (see [Request.authorization]). Instances of this type contain the information
|
/// is created and attached to the request (see [Request.authorization]). Instances of this type contain the information
|
||||||
/// that the [Authorizer] obtained from an [AuthValidator] (typically an [AuthServer])
|
/// that the [Authorizer] obtained from an [AuthValidator] (typically an [AuthServer])
|
||||||
/// about the validity of the credentials in a request.
|
/// about the validity of the credentials in a request.
|
||||||
class Authorization {
|
class Authorization {
|
||||||
/// Creates an instance of a [Authorization].
|
/// Creates an instance of [Authorization].
|
||||||
|
///
|
||||||
|
/// This constructor initializes an [Authorization] object with the provided parameters:
|
||||||
|
///
|
||||||
|
/// - [clientID]: The client ID under which the permission was granted.
|
||||||
|
/// - [ownerID]: The identifier for the owner of the resource, if provided. Can be null.
|
||||||
|
/// - [validator]: The [AuthValidator] that granted this permission.
|
||||||
|
/// - [credentials]: Optional. Basic authorization credentials, if provided.
|
||||||
|
/// - [scopes]: Optional. The list of scopes this authorization has access to.
|
||||||
|
///
|
||||||
|
/// This class is typically used to represent the authorization information for a [Request]
|
||||||
|
/// after it has passed through an [Authorizer].
|
||||||
Authorization(
|
Authorization(
|
||||||
this.clientID,
|
this.clientID,
|
||||||
this.ownerID,
|
this.ownerID,
|
||||||
|
@ -234,34 +508,71 @@ class Authorization {
|
||||||
this.scopes,
|
this.scopes,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The client ID the permission was granted under.
|
/// The client ID associated with this authorization.
|
||||||
|
///
|
||||||
|
/// This property represents the unique identifier of the OAuth 2.0 client
|
||||||
|
/// that was granted permission. It is used to link the authorization
|
||||||
|
/// back to the specific client application that requested it.
|
||||||
|
///
|
||||||
|
/// The client ID is typically assigned by the authorization server when
|
||||||
|
/// the client application is registered, and it's used to identify the
|
||||||
|
/// client throughout the OAuth 2.0 flow.
|
||||||
final String clientID;
|
final String clientID;
|
||||||
|
|
||||||
/// The identifier for the owner of the resource, if provided.
|
/// The identifier for the owner of the resource, if provided.
|
||||||
///
|
///
|
||||||
/// If this instance refers to the authorization of a resource owner, this value will
|
/// This property represents the unique identifier of the resource owner associated
|
||||||
/// be its identifying value. For example, in an application where a 'User' is stored in a database,
|
/// with this authorization. In OAuth 2.0 terminology, the resource owner is typically
|
||||||
/// this value would be the primary key of that user.
|
/// the end-user who grants permission to an application to access their data.
|
||||||
///
|
///
|
||||||
/// If this authorization does not refer to a specific resource owner, this value will be null.
|
/// If this authorization does not refer to a specific resource owner, this value will be null.
|
||||||
final int? ownerID;
|
final int? ownerID;
|
||||||
|
|
||||||
/// The [AuthValidator] that granted this permission.
|
/// The [AuthValidator] that granted this permission.
|
||||||
|
///
|
||||||
|
/// This property represents the [AuthValidator] instance that was responsible
|
||||||
|
/// for validating and granting the authorization. It can be used to trace
|
||||||
|
/// the origin of the authorization or to perform additional validation
|
||||||
|
/// if needed.
|
||||||
|
///
|
||||||
|
/// The validator might be null in cases where the authorization was not
|
||||||
|
/// granted through a standard validation process or if the information
|
||||||
|
/// about the validator is not relevant or available.
|
||||||
final AuthValidator? validator;
|
final AuthValidator? validator;
|
||||||
|
|
||||||
/// Basic authorization credentials, if provided.
|
/// Basic authorization credentials, if provided.
|
||||||
///
|
///
|
||||||
/// If this instance represents the authorization header of a request with basic authorization credentials,
|
/// This property holds the parsed basic authorization credentials if they were
|
||||||
/// the parsed credentials will be available in this property. Otherwise, this value is null.
|
/// present in the authorization header of the request. If the request did not
|
||||||
|
/// use basic authorization, or if the credentials were not successfully parsed,
|
||||||
|
/// this property will be null.
|
||||||
|
///
|
||||||
|
/// The [AuthBasicCredentials] object typically contains a username and password
|
||||||
|
/// pair extracted from the 'Authorization' header of an HTTP request using the
|
||||||
|
/// Basic authentication scheme.
|
||||||
|
///
|
||||||
|
/// This can be useful for endpoints that support both OAuth 2.0 token-based
|
||||||
|
/// authentication and traditional username/password authentication via Basic Auth.
|
||||||
final AuthBasicCredentials? credentials;
|
final AuthBasicCredentials? credentials;
|
||||||
|
|
||||||
/// The list of scopes this authorization has access to.
|
/// The list of scopes this authorization has access to.
|
||||||
///
|
///
|
||||||
/// If the access token used to create this instance has scope,
|
/// This property represents the set of permissions or access rights granted to this authorization.
|
||||||
/// those scopes will be available here. Otherwise, null.
|
/// Each [AuthScope] in the list defines a specific area of access or functionality
|
||||||
|
/// that the authorization is allowed to use.
|
||||||
|
///
|
||||||
|
/// If the access token used to create this instance has scopes associated with it,
|
||||||
|
/// those scopes will be available in this list. If no scopes were associated with
|
||||||
|
/// the access token, or if scopes are not being used in the system, this property will be null.
|
||||||
|
///
|
||||||
|
/// Scopes are crucial for implementing fine-grained access control in OAuth 2.0 systems,
|
||||||
|
/// allowing for precise definition of what each authorization is allowed to do.
|
||||||
|
///
|
||||||
|
/// This list can be used in conjunction with the [isAuthorizedForScope] method to check
|
||||||
|
/// if the authorization has access to a specific scope.
|
||||||
List<AuthScope>? scopes;
|
List<AuthScope>? scopes;
|
||||||
|
|
||||||
/// Whether or not this instance has access to a specific scope.
|
/// Determines if this authorization has access to a specific scope.
|
||||||
///
|
///
|
||||||
/// This method checks each element in [scopes] for any that gives privileges
|
/// This method checks each element in [scopes] for any that gives privileges
|
||||||
/// to access [scope].
|
/// to access [scope].
|
||||||
|
@ -271,7 +582,7 @@ class Authorization {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Instances represent OAuth 2.0 scope.
|
/// Represents and manages OAuth 2.0 scopes.
|
||||||
///
|
///
|
||||||
/// An OAuth 2.0 token may optionally have authorization scopes. An authorization scope provides more granular
|
/// An OAuth 2.0 token may optionally have authorization scopes. An authorization scope provides more granular
|
||||||
/// authorization to protected resources. Without authorization scopes, any valid token can pass through an
|
/// authorization to protected resources. Without authorization scopes, any valid token can pass through an
|
||||||
|
@ -282,7 +593,7 @@ class Authorization {
|
||||||
/// any of the scopes the client provides. Scopes are then granted to the access token. An [Authorizer] may specify
|
/// any of the scopes the client provides. Scopes are then granted to the access token. An [Authorizer] may specify
|
||||||
/// a one or more required scopes that a token must have to pass to the next controller.
|
/// a one or more required scopes that a token must have to pass to the next controller.
|
||||||
class AuthScope {
|
class AuthScope {
|
||||||
/// Creates an instance of this type from [scopeString].
|
/// Creates an instance of [AuthScope] from a [scopeString].
|
||||||
///
|
///
|
||||||
/// A simple authorization scope string is a single keyword. Valid characters are
|
/// A simple authorization scope string is a single keyword. Valid characters are
|
||||||
///
|
///
|
||||||
|
@ -320,6 +631,27 @@ class AuthScope {
|
||||||
return scope;
|
return scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses and creates an [AuthScope] instance from a given scope string.
|
||||||
|
///
|
||||||
|
/// This factory method performs several validation checks on the input [scopeString]:
|
||||||
|
/// 1. Ensures the string is not empty.
|
||||||
|
/// 2. Validates that each character in the string is within the allowed set of characters.
|
||||||
|
/// 3. Parses the string into segments and extracts the modifier (if any).
|
||||||
|
///
|
||||||
|
/// The allowed characters are: A-Za-z0-9!#$%&'`()*+,./:;<=>?@[]^_{|}-
|
||||||
|
///
|
||||||
|
/// If any validation fails, a [FormatException] is thrown with a descriptive error message.
|
||||||
|
///
|
||||||
|
/// After successful validation and parsing, it creates and returns a new [AuthScope] instance.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [scopeString]: The string representation of the scope to parse.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A new [AuthScope] instance representing the parsed scope.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// [FormatException] if the [scopeString] is empty or contains invalid characters.
|
||||||
factory AuthScope._parse(String scopeString) {
|
factory AuthScope._parse(String scopeString) {
|
||||||
if (scopeString.isEmpty) {
|
if (scopeString.isEmpty) {
|
||||||
throw FormatException(
|
throw FormatException(
|
||||||
|
@ -345,16 +677,28 @@ class AuthScope {
|
||||||
return AuthScope._(scopeString, segments, lastModifier);
|
return AuthScope._(scopeString, segments, lastModifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Private constructor for creating an [AuthScope] instance.
|
||||||
|
///
|
||||||
|
/// This constructor is used internally by the class to create instances
|
||||||
|
/// after parsing and validating the scope string.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [_scopeString]: The original, unparsed scope string.
|
||||||
|
/// [_segments]: A list of parsed [_AuthScopeSegment] objects representing the scope's segments.
|
||||||
|
/// [_lastModifier]: The modifier of the last segment, if any.
|
||||||
|
///
|
||||||
|
/// This constructor is marked as `const` to allow for compile-time constant instances,
|
||||||
|
/// which can improve performance and memory usage in certain scenarios.
|
||||||
const AuthScope._(this._scopeString, this._segments, this._lastModifier);
|
const AuthScope._(this._scopeString, this._segments, this._lastModifier);
|
||||||
|
|
||||||
/// Signifies 'any' scope in [AuthServerDelegate.getAllowedScopes].
|
/// Represents a special constant for indicating 'any' scope in [AuthServerDelegate.getAllowedScopes].
|
||||||
///
|
///
|
||||||
/// See [AuthServerDelegate.getAllowedScopes] for more details.
|
/// See [AuthServerDelegate.getAllowedScopes] for more details.
|
||||||
static const List<AuthScope> any = [
|
static const List<AuthScope> any = [
|
||||||
AuthScope._("_scope:_constant:_marker", [], null)
|
AuthScope._("_scope:_constant:_marker", [], null)
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Returns true if that [providedScopes] fulfills [requiredScopes].
|
/// Verifies if the provided scopes fulfill the required scopes.
|
||||||
///
|
///
|
||||||
/// For all [requiredScopes], there must be a scope in [requiredScopes] that meets or exceeds
|
/// For all [requiredScopes], there must be a scope in [requiredScopes] that meets or exceeds
|
||||||
/// that scope for this method to return true. If [requiredScopes] is null, this method
|
/// that scope for this method to return true. If [requiredScopes] is null, this method
|
||||||
|
@ -375,23 +719,97 @@ class AuthScope {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A cache to store previously created AuthScope instances.
|
||||||
|
///
|
||||||
|
/// This static map serves as a cache to store AuthScope instances that have been
|
||||||
|
/// previously created. The key is the string representation of the scope, and
|
||||||
|
/// the value is the corresponding AuthScope instance.
|
||||||
|
///
|
||||||
|
/// Caching AuthScope instances can improve performance by avoiding repeated
|
||||||
|
/// parsing and object creation for frequently used scopes. When an AuthScope
|
||||||
|
/// is requested with a scope string that already exists in this cache, the
|
||||||
|
/// cached instance is returned instead of creating a new one.
|
||||||
static final Map<String, AuthScope> _cache = {};
|
static final Map<String, AuthScope> _cache = {};
|
||||||
|
|
||||||
|
/// The original, unparsed scope string.
|
||||||
|
///
|
||||||
|
/// This private field stores the complete scope string as it was originally provided
|
||||||
|
/// when creating the AuthScope instance. It represents the full, unmodified scope
|
||||||
|
/// including all segments and modifiers.
|
||||||
|
///
|
||||||
|
/// This string is used for caching purposes and when converting the AuthScope
|
||||||
|
/// back to its string representation (e.g., in the toString() method).
|
||||||
final String _scopeString;
|
final String _scopeString;
|
||||||
|
|
||||||
/// Individual segments, separated by `:` character, of this instance.
|
/// Returns an iterable of individual segments of this AuthScope instance.
|
||||||
///
|
///
|
||||||
/// Will always have a length of at least 1.
|
/// Will always have a length of at least 1.
|
||||||
Iterable<String?> get segments => _segments.map((s) => s.name);
|
Iterable<String?> get segments => _segments.map((s) => s.name);
|
||||||
|
|
||||||
/// The modifier of this scope, if it exists.
|
/// Returns the modifier of this scope, if it exists.
|
||||||
///
|
///
|
||||||
/// If this instance does not have a modifier, returns null.
|
/// The modifier is an optional component of an AuthScope that provides additional
|
||||||
|
/// specification or restriction to the scope. It is typically the last part of a
|
||||||
|
/// scope string, following a dot (.) after the last segment.
|
||||||
|
///
|
||||||
|
/// For example, in the scope "user:profile.readonly", "readonly" is the modifier.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A [String] representing the modifier if one exists, or null if this AuthScope
|
||||||
|
/// does not have a modifier.
|
||||||
|
///
|
||||||
|
/// This getter provides access to the private [_lastModifier] field, allowing
|
||||||
|
/// external code to check for the presence and value of a modifier without
|
||||||
|
/// directly accessing the internal state of the AuthScope.
|
||||||
String? get modifier => _lastModifier;
|
String? get modifier => _lastModifier;
|
||||||
|
|
||||||
|
/// List of segments that make up this AuthScope.
|
||||||
|
///
|
||||||
|
/// This private field stores the parsed segments of the scope string as a list of
|
||||||
|
/// [_AuthScopeSegment] objects. Each segment represents a part of the scope,
|
||||||
|
/// separated by colons in the original scope string.
|
||||||
|
///
|
||||||
|
/// For example, for a scope string "user:profile:read", this list would contain
|
||||||
|
/// three _AuthScopeSegment objects representing "user", "profile", and "read"
|
||||||
|
/// respectively.
|
||||||
|
///
|
||||||
|
/// This list is used internally for scope comparisons and validations.
|
||||||
final List<_AuthScopeSegment> _segments;
|
final List<_AuthScopeSegment> _segments;
|
||||||
|
|
||||||
|
/// The modifier of the last segment in this AuthScope.
|
||||||
|
///
|
||||||
|
/// This private field stores the modifier of the last segment in the AuthScope,
|
||||||
|
/// if one exists. A modifier provides additional specification or restriction
|
||||||
|
/// to a scope and is typically the part following a dot (.) in the last segment
|
||||||
|
/// of a scope string.
|
||||||
|
///
|
||||||
|
/// For example, in the scope "user:profile.readonly", "readonly" would be stored
|
||||||
|
/// in this field.
|
||||||
|
///
|
||||||
|
/// The value is null if the AuthScope does not have a modifier in its last segment.
|
||||||
final String? _lastModifier;
|
final String? _lastModifier;
|
||||||
|
|
||||||
|
/// Parses the given scope string into a list of [_AuthScopeSegment] objects.
|
||||||
|
///
|
||||||
|
/// This method performs the following steps:
|
||||||
|
/// 1. Checks if the input string is empty and throws a [FormatException] if it is.
|
||||||
|
/// 2. Splits the string by ':' and creates [_AuthScopeSegment] objects for each segment.
|
||||||
|
/// 3. Validates each segment, ensuring:
|
||||||
|
/// - Only the last segment can have a modifier.
|
||||||
|
/// - There are no empty segments.
|
||||||
|
/// - There are no leading or trailing colons.
|
||||||
|
///
|
||||||
|
/// If any validation fails, a [FormatException] is thrown with a descriptive error message
|
||||||
|
/// and the position in the string where the error occurred.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [scopeString]: The string representation of the scope to parse.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A list of [_AuthScopeSegment] objects representing the parsed segments of the scope.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// [FormatException] if the [scopeString] is empty or contains invalid segments.
|
||||||
static List<_AuthScopeSegment> _parseSegments(String scopeString) {
|
static List<_AuthScopeSegment> _parseSegments(String scopeString) {
|
||||||
if (scopeString.isEmpty) {
|
if (scopeString.isEmpty) {
|
||||||
throw FormatException(
|
throw FormatException(
|
||||||
|
@ -435,7 +853,7 @@ class AuthScope {
|
||||||
return elements;
|
return elements;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether or not this instance is a subset or equal to [incomingScope].
|
/// Determines if this [AuthScope] is a subset of or equal to the [incomingScope].
|
||||||
///
|
///
|
||||||
/// The scope `users:posts` is a subset of `users`.
|
/// The scope `users:posts` is a subset of `users`.
|
||||||
///
|
///
|
||||||
|
@ -477,16 +895,42 @@ class AuthScope {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Alias of [isSubsetOrEqualTo].
|
/// Alias of [isSubsetOrEqualTo].
|
||||||
|
///
|
||||||
|
/// This method is deprecated and will be removed in a future version.
|
||||||
|
/// Use [isSubsetOrEqualTo] instead.
|
||||||
|
///
|
||||||
|
/// Determines if this [AuthScope] allows the [incomingScope].
|
||||||
|
/// It is equivalent to calling [isSubsetOrEqualTo] with the same argument.
|
||||||
|
///
|
||||||
|
/// [incomingScope]: The AuthScope to compare against this instance.
|
||||||
|
///
|
||||||
|
/// Returns true if this AuthScope is a subset of or equal to the [incomingScope],
|
||||||
|
/// false otherwise.
|
||||||
@Deprecated('Use AuthScope.isSubsetOrEqualTo() instead')
|
@Deprecated('Use AuthScope.isSubsetOrEqualTo() instead')
|
||||||
bool allowsScope(AuthScope incomingScope) => isSubsetOrEqualTo(incomingScope);
|
bool allowsScope(AuthScope incomingScope) => isSubsetOrEqualTo(incomingScope);
|
||||||
|
|
||||||
/// String variant of [isSubsetOrEqualTo].
|
/// Checks if this AuthScope is a subset of or equal to the given scope string.
|
||||||
///
|
///
|
||||||
/// Parses an instance of this type from [scopeString] and invokes
|
/// Parses an instance of this type from [scopeString] and invokes
|
||||||
/// [isSubsetOrEqualTo].
|
/// [isSubsetOrEqualTo].
|
||||||
bool allows(String scopeString) => isSubsetOrEqualTo(AuthScope(scopeString));
|
bool allows(String scopeString) => isSubsetOrEqualTo(AuthScope(scopeString));
|
||||||
|
|
||||||
/// Whether or not two scopes are exactly the same.
|
/// Determines if this [AuthScope] is exactly the same as the given [scope].
|
||||||
|
///
|
||||||
|
/// This method compares each segment and modifier of both scopes to ensure they are identical.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [scope]: The [AuthScope] to compare against this instance.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// [bool]: true if both scopes are exactly the same, false otherwise.
|
||||||
|
///
|
||||||
|
/// The comparison is performed as follows:
|
||||||
|
/// 1. Iterates through each segment of both scopes simultaneously.
|
||||||
|
/// 2. If the given scope has fewer segments, returns false.
|
||||||
|
/// 3. Compares the name and modifier of each segment.
|
||||||
|
/// 4. If any segment's name or modifier doesn't match, returns false.
|
||||||
|
/// 5. If all segments match and both scopes have the same number of segments, returns true.
|
||||||
bool isExactlyScope(AuthScope scope) {
|
bool isExactlyScope(AuthScope scope) {
|
||||||
final incomingIterator = scope._segments.iterator;
|
final incomingIterator = scope._segments.iterator;
|
||||||
for (final segment in _segments) {
|
for (final segment in _segments) {
|
||||||
|
@ -506,18 +950,44 @@ class AuthScope {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// String variant of [isExactlyScope].
|
/// Checks if this AuthScope is exactly the same as the given scope string.
|
||||||
///
|
///
|
||||||
/// Parses an instance of this type from [scopeString] and invokes [isExactlyScope].
|
/// Parses an instance of this type from [scopeString] and invokes [isExactlyScope].
|
||||||
bool isExactly(String scopeString) {
|
bool isExactly(String scopeString) {
|
||||||
return isExactlyScope(AuthScope(scopeString));
|
return isExactlyScope(AuthScope(scopeString));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a string representation of this AuthScope.
|
||||||
|
///
|
||||||
|
/// This method overrides the default [Object.toString] method to provide
|
||||||
|
/// a string representation of the AuthScope instance. It returns the
|
||||||
|
/// original, unparsed scope string that was used to create this AuthScope.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A [String] representing the complete scope, including all segments
|
||||||
|
/// and modifiers, exactly as it was originally provided.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// final scope = AuthScope('user:profile.readonly');
|
||||||
|
/// print(scope.toString()); // Outputs: 'user:profile.readonly'
|
||||||
@override
|
@override
|
||||||
String toString() => _scopeString;
|
String toString() => _scopeString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a segment of an AuthScope.
|
||||||
|
///
|
||||||
|
/// An AuthScope can be composed of one or more segments, where each segment
|
||||||
|
/// may have a name and an optional modifier. This class parses and stores
|
||||||
|
/// the components of a single segment.
|
||||||
class _AuthScopeSegment {
|
class _AuthScopeSegment {
|
||||||
|
/// Constructs an AuthScopeSegment from the given [segment] string.
|
||||||
|
///
|
||||||
|
/// The [segment] string is expected to be in the format "name.modifier" or "name".
|
||||||
|
/// If a modifier is present, it is stored in the [modifier] field. If not, [modifier]
|
||||||
|
/// remains null. The [name] field always contains the name of the segment.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [segment]: A [String] representing the segment, which may include a modifier.
|
||||||
_AuthScopeSegment(String segment) {
|
_AuthScopeSegment(String segment) {
|
||||||
final split = segment.split(".");
|
final split = segment.split(".");
|
||||||
if (split.length == 2) {
|
if (split.length == 2) {
|
||||||
|
@ -528,9 +998,42 @@ class _AuthScopeSegment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The name of the segment.
|
||||||
|
///
|
||||||
|
/// This property represents the main part of the segment before any modifier.
|
||||||
|
/// For example, in the segment "user.readonly", "user" would be the name.
|
||||||
|
///
|
||||||
|
/// This value can be null if the segment is empty or malformed.
|
||||||
String? name;
|
String? name;
|
||||||
|
|
||||||
|
/// The modifier of the segment, if present.
|
||||||
|
///
|
||||||
|
/// This property represents the optional part of the segment after the name,
|
||||||
|
/// if a modifier is specified. For example, in the segment "user.readonly",
|
||||||
|
/// "readonly" would be the modifier.
|
||||||
|
///
|
||||||
|
/// If no modifier is present in the segment, this value is null.
|
||||||
String? modifier;
|
String? modifier;
|
||||||
|
|
||||||
|
/// Returns a string representation of this AuthScopeSegment.
|
||||||
|
///
|
||||||
|
/// This method overrides the default [Object.toString] method to provide
|
||||||
|
/// a string representation of the AuthScopeSegment instance.
|
||||||
|
///
|
||||||
|
/// If the segment has a modifier, it returns the name and modifier
|
||||||
|
/// separated by a dot (e.g., "name.modifier").
|
||||||
|
/// If there's no modifier, it returns just the name.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A [String] representing the complete segment, including the
|
||||||
|
/// modifier if present.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// final segment = _AuthScopeSegment('user.readonly');
|
||||||
|
/// print(segment.toString()); // Outputs: 'user.readonly'
|
||||||
|
///
|
||||||
|
/// final segmentNoModifier = _AuthScopeSegment('user');
|
||||||
|
/// print(segmentNoModifier.toString()); // Outputs: 'user'
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
if (modifier == null) {
|
if (modifier == null) {
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:protevus_auth/auth.dart';
|
import 'package:protevus_auth/auth.dart';
|
||||||
|
|
||||||
/// The properties of an OAuth 2.0 Resource Owner.
|
/// Defines the interface for a Resource Owner in OAuth 2.0 authentication.
|
||||||
///
|
///
|
||||||
/// Your application's 'user' type must implement the methods declared in this interface. [AuthServer] can
|
/// Your application's 'user' type must implement the methods declared in this interface. [AuthServer] can
|
||||||
/// validate the credentials of a [ResourceOwner] to grant authorization codes and access tokens on behalf of that
|
/// validate the credentials of a [ResourceOwner] to grant authorization codes and access tokens on behalf of that
|
||||||
|
@ -9,20 +18,59 @@ import 'package:protevus_auth/auth.dart';
|
||||||
abstract class ResourceOwner {
|
abstract class ResourceOwner {
|
||||||
/// The username of the resource owner.
|
/// The username of the resource owner.
|
||||||
///
|
///
|
||||||
/// This value must be unique amongst all resource owners. It is often an email address. This value
|
/// This property represents the unique identifier for a resource owner, typically used for authentication purposes.
|
||||||
/// is used by authenticating users to identify their account.
|
/// It must be unique among all resource owners in the system. Often, this value is an email address.
|
||||||
|
///
|
||||||
|
/// The username is used by authenticating users to identify their account when logging in or performing
|
||||||
|
/// other authentication-related actions.
|
||||||
|
///
|
||||||
|
/// This property is nullable, which means it can be null in some cases, such as when creating a new
|
||||||
|
/// resource owner instance before setting the username.
|
||||||
String? username;
|
String? username;
|
||||||
|
|
||||||
/// The hashed password of this instance.
|
/// The hashed password of this instance.
|
||||||
|
///
|
||||||
|
/// This property stores the password of the resource owner in a hashed format.
|
||||||
|
/// Hashing is a one-way process that converts the plain text password into a
|
||||||
|
/// fixed-length string of characters, which is more secure to store than the
|
||||||
|
/// original password.
|
||||||
|
///
|
||||||
|
/// The hashed password is used for password verification during authentication
|
||||||
|
/// without storing the actual password. This enhances security by ensuring
|
||||||
|
/// that even if the database is compromised, the original passwords remain
|
||||||
|
/// unknown.
|
||||||
|
///
|
||||||
|
/// This property is nullable, allowing for cases where a password might not
|
||||||
|
/// be set or required for certain types of resource owners.
|
||||||
String? hashedPassword;
|
String? hashedPassword;
|
||||||
|
|
||||||
/// The salt the [hashedPassword] was hashed with.
|
/// The salt used in the hashing process for [hashedPassword].
|
||||||
|
///
|
||||||
|
/// A salt is a random string that is added to the password before hashing,
|
||||||
|
/// which adds an extra layer of security to the hashed password. It helps
|
||||||
|
/// protect against rainbow table attacks and ensures that even if two users
|
||||||
|
/// have the same password, their hashed passwords will be different.
|
||||||
|
///
|
||||||
|
/// This property is nullable to accommodate cases where a salt might not be
|
||||||
|
/// used or stored separately from the hashed password.
|
||||||
String? salt;
|
String? salt;
|
||||||
|
|
||||||
/// A unique identifier of this resource owner.
|
/// A unique identifier of this resource owner.
|
||||||
///
|
///
|
||||||
/// This unique identifier is used by [AuthServer] to associate authorization codes and access tokens with
|
/// This property represents a unique identifier for the resource owner, typically
|
||||||
/// this resource owner.
|
/// used in authentication and authorization processes. The [AuthServer] uses this
|
||||||
|
/// identifier to associate authorization codes and access tokens with the specific
|
||||||
|
/// resource owner.
|
||||||
|
///
|
||||||
|
/// The identifier is of type [int] and is nullable, allowing for cases where an ID
|
||||||
|
/// might not be assigned yet (e.g., when creating a new resource owner instance).
|
||||||
|
///
|
||||||
|
/// It's crucial to ensure that this ID remains unique across all resource owners
|
||||||
|
/// in the system to maintain the integrity of the authentication and authorization
|
||||||
|
/// processes.
|
||||||
|
///
|
||||||
|
/// This getter method should be implemented to return the unique identifier of
|
||||||
|
/// the resource owner.
|
||||||
int? get id;
|
int? get id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,8 +81,23 @@ abstract class ResourceOwner {
|
||||||
///
|
///
|
||||||
/// Prefer to use `ManagedAuthDelegate` from 'package:conduit_core/managed_auth.dart' instead of implementing this interface;
|
/// Prefer to use `ManagedAuthDelegate` from 'package:conduit_core/managed_auth.dart' instead of implementing this interface;
|
||||||
/// there are important details to consider and test when implementing this interface.
|
/// there are important details to consider and test when implementing this interface.
|
||||||
|
///
|
||||||
|
/// This abstract class defines the contract for implementing an authentication and authorization system.
|
||||||
|
/// It provides methods for managing resource owners, clients, tokens, and authorization codes.
|
||||||
|
/// Implementations of this class are responsible for handling the storage, retrieval, and management
|
||||||
|
/// of these entities, as well as customizing certain behaviors of the authentication process.
|
||||||
|
///
|
||||||
|
/// Key responsibilities include:
|
||||||
|
/// - Managing resource owners (users)
|
||||||
|
/// - Handling client applications
|
||||||
|
/// - Storing and retrieving access and refresh tokens
|
||||||
|
/// - Managing authorization codes
|
||||||
|
/// - Customizing token formats and allowed scopes
|
||||||
|
///
|
||||||
|
/// Each method in this class corresponds to a specific operation in the OAuth 2.0 flow,
|
||||||
|
/// allowing for a flexible and extensible authentication system.
|
||||||
abstract class AuthServerDelegate {
|
abstract class AuthServerDelegate {
|
||||||
/// Must return a [ResourceOwner] for a [username].
|
/// Retrieves a [ResourceOwner] based on the provided [username].
|
||||||
///
|
///
|
||||||
/// This method must return an instance of [ResourceOwner] if one exists for [username]. Otherwise, it must return null.
|
/// This method must return an instance of [ResourceOwner] if one exists for [username]. Otherwise, it must return null.
|
||||||
///
|
///
|
||||||
|
@ -43,19 +106,19 @@ abstract class AuthServerDelegate {
|
||||||
/// [server] is the [AuthServer] invoking this method.
|
/// [server] is the [AuthServer] invoking this method.
|
||||||
FutureOr<ResourceOwner?> getResourceOwner(AuthServer server, String username);
|
FutureOr<ResourceOwner?> getResourceOwner(AuthServer server, String username);
|
||||||
|
|
||||||
/// Must store [client].
|
/// Stores a new [AuthClient] in the system.
|
||||||
///
|
///
|
||||||
/// [client] must be returned by [getClient] after this method has been invoked, and until (if ever)
|
/// [client] must be returned by [getClient] after this method has been invoked, and until (if ever)
|
||||||
/// [removeClient] is invoked.
|
/// [removeClient] is invoked.
|
||||||
FutureOr addClient(AuthServer server, AuthClient client);
|
FutureOr addClient(AuthServer server, AuthClient client);
|
||||||
|
|
||||||
/// Must return [AuthClient] for a client ID.
|
/// Retrieves an [AuthClient] based on the provided client ID.
|
||||||
///
|
///
|
||||||
/// This method must return an instance of [AuthClient] if one exists for [clientID]. Otherwise, it must return null.
|
/// This method must return an instance of [AuthClient] if one exists for [clientID]. Otherwise, it must return null.
|
||||||
/// [server] is the [AuthServer] requesting the [AuthClient].
|
/// [server] is the [AuthServer] requesting the [AuthClient].
|
||||||
FutureOr<AuthClient?> getClient(AuthServer server, String clientID);
|
FutureOr<AuthClient?> getClient(AuthServer server, String clientID);
|
||||||
|
|
||||||
/// Removes an [AuthClient] for a client ID.
|
/// Removes an [AuthClient] for a given client ID.
|
||||||
///
|
///
|
||||||
/// This method must delete the [AuthClient] for [clientID]. Subsequent requests to this
|
/// This method must delete the [AuthClient] for [clientID]. Subsequent requests to this
|
||||||
/// instance for [getClient] must return null after this method completes. If there is no
|
/// instance for [getClient] must return null after this method completes. If there is no
|
||||||
|
@ -64,7 +127,7 @@ abstract class AuthServerDelegate {
|
||||||
/// [server] is the [AuthServer] requesting the [AuthClient].
|
/// [server] is the [AuthServer] requesting the [AuthClient].
|
||||||
FutureOr removeClient(AuthServer server, String clientID);
|
FutureOr removeClient(AuthServer server, String clientID);
|
||||||
|
|
||||||
/// Returns a [AuthToken] searching by its access token or refresh token.
|
/// Retrieves an [AuthToken] based on either its access token or refresh token.
|
||||||
///
|
///
|
||||||
/// Exactly one of [byAccessToken] and [byRefreshToken] may be non-null, if not, this method must throw an error.
|
/// Exactly one of [byAccessToken] and [byRefreshToken] may be non-null, if not, this method must throw an error.
|
||||||
///
|
///
|
||||||
|
@ -80,12 +143,12 @@ abstract class AuthServerDelegate {
|
||||||
String? byRefreshToken,
|
String? byRefreshToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// This method must delete all [AuthToken] and [AuthCode]s for a [ResourceOwner].
|
/// Deletes all [AuthToken]s and [AuthCode]s associated with a specific [ResourceOwner].
|
||||||
///
|
///
|
||||||
/// [server] is the requesting [AuthServer]. [resourceOwnerID] is the [ResourceOwner.id].
|
/// [server] is the requesting [AuthServer]. [resourceOwnerID] is the [ResourceOwner.id].
|
||||||
FutureOr removeTokens(AuthServer server, int resourceOwnerID);
|
FutureOr removeTokens(AuthServer server, int resourceOwnerID);
|
||||||
|
|
||||||
/// Must delete a [AuthToken] granted by [grantedByCode].
|
/// Deletes an [AuthToken] that was granted by a specific [AuthCode].
|
||||||
///
|
///
|
||||||
/// If an [AuthToken] has been granted by exchanging [AuthCode], that token must be revoked
|
/// If an [AuthToken] has been granted by exchanging [AuthCode], that token must be revoked
|
||||||
/// and can no longer be used to authorize access to a resource. [grantedByCode] should
|
/// and can no longer be used to authorize access to a resource. [grantedByCode] should
|
||||||
|
@ -94,7 +157,7 @@ abstract class AuthServerDelegate {
|
||||||
/// This method is invoked when attempting to exchange an authorization code that has already granted a token.
|
/// This method is invoked when attempting to exchange an authorization code that has already granted a token.
|
||||||
FutureOr removeToken(AuthServer server, AuthCode grantedByCode);
|
FutureOr removeToken(AuthServer server, AuthCode grantedByCode);
|
||||||
|
|
||||||
/// Must store [token].
|
/// Stores an [AuthToken] in the system.
|
||||||
///
|
///
|
||||||
/// [token] must be stored such that it is accessible from [getToken], and until it is either
|
/// [token] must be stored such that it is accessible from [getToken], and until it is either
|
||||||
/// revoked via [removeToken] or [removeTokens], or until it has expired and can reasonably
|
/// revoked via [removeToken] or [removeTokens], or until it has expired and can reasonably
|
||||||
|
@ -107,7 +170,7 @@ abstract class AuthServerDelegate {
|
||||||
/// is null.
|
/// is null.
|
||||||
FutureOr addToken(AuthServer server, AuthToken token, {AuthCode? issuedFrom});
|
FutureOr addToken(AuthServer server, AuthToken token, {AuthCode? issuedFrom});
|
||||||
|
|
||||||
/// Must update [AuthToken] with [newAccessToken, [newIssueDate, [newExpirationDate].
|
/// Updates an existing [AuthToken] with new values.
|
||||||
///
|
///
|
||||||
/// This method must must update an existing [AuthToken], found by [oldAccessToken],
|
/// This method must must update an existing [AuthToken], found by [oldAccessToken],
|
||||||
/// with the values [newAccessToken], [newIssueDate] and [newExpirationDate].
|
/// with the values [newAccessToken], [newIssueDate] and [newExpirationDate].
|
||||||
|
@ -122,23 +185,23 @@ abstract class AuthServerDelegate {
|
||||||
DateTime? newExpirationDate,
|
DateTime? newExpirationDate,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Must store [code].
|
/// Stores an [AuthCode] in the system.
|
||||||
///
|
///
|
||||||
/// [code] must be accessible until its expiration date.
|
/// [code] must be accessible until its expiration date.
|
||||||
FutureOr addCode(AuthServer server, AuthCode code);
|
FutureOr addCode(AuthServer server, AuthCode code);
|
||||||
|
|
||||||
/// Must return [AuthCode] for its identifiying [code].
|
/// Retrieves an [AuthCode] based on its identifying code.
|
||||||
///
|
///
|
||||||
/// This must return an instance of [AuthCode] where [AuthCode.code] matches [code].
|
/// This must return an instance of [AuthCode] where [AuthCode.code] matches [code].
|
||||||
/// Return null if no matching code.
|
/// Return null if no matching code.
|
||||||
FutureOr<AuthCode?> getCode(AuthServer server, String code);
|
FutureOr<AuthCode?> getCode(AuthServer server, String code);
|
||||||
|
|
||||||
/// Must remove [AuthCode] identified by [code].
|
/// Removes an [AuthCode] from the system based on its identifying code.
|
||||||
///
|
///
|
||||||
/// The [AuthCode.code] matching [code] must be deleted and no longer accessible.
|
/// The [AuthCode.code] matching [code] must be deleted and no longer accessible.
|
||||||
FutureOr removeCode(AuthServer server, String? code);
|
FutureOr removeCode(AuthServer server, String? code);
|
||||||
|
|
||||||
/// Returns list of allowed scopes for a given [ResourceOwner].
|
/// Returns a list of allowed scopes for a given [ResourceOwner].
|
||||||
///
|
///
|
||||||
/// Subclasses override this method to return a list of [AuthScope]s based on some attribute(s) of an [ResourceOwner].
|
/// Subclasses override this method to return a list of [AuthScope]s based on some attribute(s) of an [ResourceOwner].
|
||||||
/// That [ResourceOwner] is then restricted to only those scopes, even if the authenticating client would allow other scopes
|
/// That [ResourceOwner] is then restricted to only those scopes, even if the authenticating client would allow other scopes
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:protevus_openapi/documentable.dart';
|
import 'package:protevus_openapi/documentable.dart';
|
||||||
|
@ -5,14 +14,14 @@ import 'package:protevus_auth/auth.dart';
|
||||||
import 'package:protevus_http/http.dart';
|
import 'package:protevus_http/http.dart';
|
||||||
import 'package:protevus_openapi/v3.dart';
|
import 'package:protevus_openapi/v3.dart';
|
||||||
|
|
||||||
/// Instances that implement this type can be used by an [Authorizer] to determine authorization of a request.
|
/// A mixin that defines the interface for validating authorization data.
|
||||||
///
|
///
|
||||||
/// When an [Authorizer] processes a [Request], it invokes [validate], passing in the parsed Authorization
|
/// When an [Authorizer] processes a [Request], it invokes [validate], passing in the parsed Authorization
|
||||||
/// header of the [Request].
|
/// header of the [Request].
|
||||||
///
|
///
|
||||||
/// [AuthServer] implements this interface.
|
/// [AuthServer] implements this interface.
|
||||||
mixin AuthValidator {
|
mixin AuthValidator {
|
||||||
/// Returns an [Authorization] if [authorizationData] is valid.
|
/// Validates authorization data and returns an [Authorization] if valid.
|
||||||
///
|
///
|
||||||
/// This method is invoked by [Authorizer] to validate the Authorization header of a request. [authorizationData]
|
/// This method is invoked by [Authorizer] to validate the Authorization header of a request. [authorizationData]
|
||||||
/// is the parsed contents of the Authorization header, while [parser] is the object that parsed the header.
|
/// is the parsed contents of the Authorization header, while [parser] is the object that parsed the header.
|
||||||
|
@ -28,7 +37,7 @@ mixin AuthValidator {
|
||||||
List<AuthScope>? requiredScope,
|
List<AuthScope>? requiredScope,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Provide [APISecurityRequirement]s for [authorizer].
|
/// Provides [APISecurityRequirement]s for the given [authorizer].
|
||||||
///
|
///
|
||||||
/// An [Authorizer] that adds security requirements to operations will invoke this method to allow this validator to define those requirements.
|
/// An [Authorizer] that adds security requirements to operations will invoke this method to allow this validator to define those requirements.
|
||||||
/// The [Authorizer] must provide the [context] it was given to document the operations, itself and optionally a list of [scopes] required to pass it.
|
/// The [Authorizer] must provide the [context] it was given to document the operations, itself and optionally a list of [scopes] required to pass it.
|
||||||
|
|
Loading…
Reference in a new issue