add: adding cookie package

This commit is contained in:
Patrick Stewart 2024-12-15 23:44:57 -07:00
parent 40168bfe8d
commit 566fd273ab
12 changed files with 490 additions and 0 deletions

7
packages/cookie/.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
# https://dart.dev/guides/libraries/private-files
# Created by `dart pub`
.dart_tool/
# Avoid committing pubspec.lock for library packages; see
# https://dart.dev/guides/libraries/private-files#pubspeclock.
pubspec.lock

View file

@ -0,0 +1,3 @@
## 1.0.0
- Initial version.

View file

@ -0,0 +1,10 @@
The MIT License (MIT)
The Laravel Framework is Copyright (c) Taylor Otwell
The Fabric Framework is Copyright (c) Vieo, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

101
packages/cookie/README.md Normal file
View file

@ -0,0 +1,101 @@
# Platform Cookie
A Dart implementation of Laravel-inspired cookie management for the Protevus platform.
## Features
- Create and manage cookies with various options (domain, path, secure, httpOnly, sameSite, etc.)
- Queue and unqueue cookies
- Handle cookie value prefixes
- Proper encoding of cookie values
- Expiration time handling
## Installation
Add this package to your `pubspec.yaml`:
```yaml
dependencies:
platform_cookie: ^1.0.0
```
Then run:
```
dart pub get
```
## Usage
Here's a basic example of how to use the `CookieJar` class:
```dart
import 'package:platform_cookie/platform_cookie.dart';
void main() {
final cookieJar = CookieJar(
domain: 'example.com',
secure: true,
httpOnly: true,
sameSite: 'Lax',
);
// Create a simple cookie
final simpleCookie = cookieJar.make('simple_cookie', 'Hello, Protevus!');
print('Simple Cookie: $simpleCookie');
// Create a cookie with custom options
final customCookie = cookieJar.make(
'custom_cookie',
'Custom Value',
path: '/admin',
minutes: 60,
httpOnly: false,
);
print('Custom Cookie: $customCookie');
// Queue a cookie
cookieJar.queue('queued_cookie', 'Queued Value', {'minutes': 30});
print('Has Queued Cookie: ${cookieJar.hasQueued('queued_cookie')}');
// Get all queued cookies
final queuedCookies = cookieJar.getQueuedCookies();
print('Queued Cookies: $queuedCookies');
// Unqueue a cookie
cookieJar.unqueue('queued_cookie');
print('Has Queued Cookie after unqueue: ${cookieJar.hasQueued('queued_cookie')}');
// Using CookieValuePrefix
final cookieName = 'prefixed_cookie';
final cookieValue = 'Prefixed Value';
final key = 'secret_key';
final prefix = CookieValuePrefix.create(cookieName, key);
final prefixedValue = '$prefix$cookieValue';
print('Prefixed Cookie Value: $prefixedValue');
// Validating and removing prefix
final validatedValue = CookieValuePrefix.validate(cookieName, prefixedValue, [key]);
print('Validated Cookie Value: $validatedValue');
}
```
For more detailed examples, check the `example` folder in the package repository.
## Testing
To run the tests for this package, use the following command:
```
dart test
```
## Contributing
Contributions are welcome! Please read our contributing guidelines before submitting pull requests.
## License
This project is licensed under the MIT License.

View file

@ -0,0 +1,30 @@
# This file configures the static analysis results for your project (errors,
# warnings, and lints).
#
# This enables the 'recommended' set of lints from `package:lints`.
# This set helps identify many issues that may lead to problems when running
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
# style and format.
#
# If you want a smaller set of lints you can change this to specify
# 'package:lints/core.yaml'. These are just the most critical lints
# (the recommended set includes the core lints).
# The core lints are also what is used by pub.dev for scoring packages.
include: package:lints/recommended.yaml
# Uncomment the following section to specify additional rules.
# linter:
# rules:
# - camel_case_types
# analyzer:
# exclude:
# - path/to/excluded/files/**
# For more information about the core and recommended set of lints, see
# https://dart.dev/go/core-lints
# For additional information about configuring this file, see
# https://dart.dev/guides/language/analysis-options

View file

@ -0,0 +1,63 @@
import 'package:platform_cookie/platform_cookie.dart';
void main() {
// Create a CookieJar instance
final cookieJar = CookieJar(
domain: 'example.com',
secure: true,
httpOnly: true,
sameSite: 'Lax',
);
// Example 1: Create a simple cookie
final simpleCookie = cookieJar.make('simple_cookie', 'Hello, Protevus!');
print('Simple Cookie: $simpleCookie');
// Example 2: Create a cookie with custom options
final customCookie = cookieJar.make(
'custom_cookie',
'Custom Value',
path: '/admin',
minutes: 60,
httpOnly: false,
);
print('Custom Cookie: $customCookie');
// Example 3: Queue a cookie
cookieJar.queue('queued_cookie', 'Queued Value', {'minutes': 30});
print('Has Queued Cookie: ${cookieJar.hasQueued('queued_cookie')}');
// Example 4: Get all queued cookies
final queuedCookies = cookieJar.getQueuedCookies();
print('Queued Cookies: $queuedCookies');
// Example 5: Unqueue a cookie
cookieJar.unqueue('queued_cookie');
print(
'Has Queued Cookie after unqueue: ${cookieJar.hasQueued('queued_cookie')}');
// Example 6: Using CookieValuePrefix
final cookieName = 'prefixed_cookie';
final cookieValue = 'Prefixed Value';
final key = 'secret_key';
final prefix = CookieValuePrefix.create(cookieName, key);
final prefixedValue = '$prefix$cookieValue';
print('Prefixed Cookie Value: $prefixedValue');
// Example 7: Validating and removing prefix
final validatedValue =
CookieValuePrefix.validate(cookieName, prefixedValue, [key]);
print('Validated Cookie Value: $validatedValue');
// Example 8: Creating a cookie with raw value
final rawCookie =
cookieJar.make('raw_cookie', 'Raw Value with spaces', raw: true);
print('Raw Cookie: $rawCookie');
// Example 9: Creating a cookie with encoded value
final encodedCookie =
cookieJar.make('encoded_cookie', 'Encoded Value with spaces');
print('Encoded Cookie: $encodedCookie');
}

View file

@ -0,0 +1,4 @@
library platform_cookie;
export 'src/cookie_jar.dart';
export 'src/cookie_value_prefix.dart';

View file

@ -0,0 +1,100 @@
import 'package:platform_contracts/contracts.dart';
import 'package:platform_macroable/platform_macroable.dart';
import 'package:platform_support/platform_support.dart';
class CookieJar
with Macroable, InteractsWithTime
implements QueueingFactory, CookieFactory {
final Map<String, Map<String, dynamic>> _queued = {};
final String _path;
final String _domain;
final bool _secure;
final bool _httpOnly;
final String? _sameSite;
CookieJar({
String path = '/',
String? domain,
bool secure = false,
bool httpOnly = true,
String? sameSite,
}) : _path = path,
_domain = domain ?? '',
_secure = secure,
_httpOnly = httpOnly,
_sameSite = sameSite;
@override
Map<String, String> make(
String name,
String value, {
String? domain,
bool? httpOnly,
int? minutes,
String? path,
bool? raw,
String? sameSite,
bool? secure,
}) {
final cookie = <String, String>{
'name': name,
'value': _encodeValue(value, raw ?? false),
'domain': domain ?? _domain,
'path': path ?? _path,
'expires': _getExpirationTime(minutes),
'secure': (secure ?? _secure).toString(),
'httponly': (httpOnly ?? _httpOnly).toString(),
'samesite': sameSite ?? _sameSite ?? '',
};
return Map.fromEntries(
cookie.entries.where((entry) => entry.value.isNotEmpty));
}
@override
void queue(String name, String value, [Map<String, dynamic>? options]) {
_queued[name] = {'value': value, ...?options};
}
bool hasQueued(String key) {
return _queued.containsKey(key);
}
@override
Map<String, dynamic> getQueuedCookies() {
return Map.fromEntries(_queued.entries.map((entry) => MapEntry(
entry.key,
make(
entry.key,
entry.value['value'] as String,
domain: entry.value['domain'] as String?,
httpOnly: entry.value['httpOnly'] as bool?,
minutes: entry.value['minutes'] as int?,
path: entry.value['path'] as String?,
raw: entry.value['raw'] as bool?,
sameSite: entry.value['sameSite'] as String?,
secure: entry.value['secure'] as bool?,
))));
}
@override
void unqueue(String name) {
_queued.remove(name);
}
String _encodeValue(String value, bool raw) {
return raw ? value : Uri.encodeComponent(value);
}
String _getExpirationTime(int? minutes) {
if (minutes != null) {
return currentTime()
.add(Duration(minutes: minutes))
.toUtc()
.toIso8601String();
}
return '';
}
// Additional methods can be added here as needed
}

View file

@ -0,0 +1,34 @@
import 'dart:convert';
import 'package:crypto/crypto.dart';
class CookieValuePrefix {
/// Create a new cookie value prefix for the given cookie name.
static String create(String cookieName, String key) {
final hmac = Hmac(sha1, utf8.encode(key));
final digest = hmac.convert(utf8.encode('${cookieName}v2'));
return '${digest.toString()}|';
}
/// Remove the cookie value prefix.
static String remove(String cookieValue) {
final separatorIndex = cookieValue.indexOf('|');
if (separatorIndex == -1 || separatorIndex == cookieValue.length - 1) {
return cookieValue; // Return original value if no separator or separator at the end
}
return cookieValue.substring(separatorIndex + 1);
}
/// Validate a cookie value contains a valid prefix.
/// If it does, return the cookie value with the prefix removed.
/// Otherwise, return null.
static String? validate(
String cookieName, String cookieValue, List<String> keys) {
for (final key in keys) {
final prefix = create(cookieName, key);
if (cookieValue.startsWith(prefix)) {
return remove(cookieValue);
}
}
return null;
}
}

View file

@ -0,0 +1,18 @@
name: platform_cookie
description: A Dart implementation of Laravel-inspired cookie management for the Protevus platform.
version: 1.0.0
homepage: https://protevus.com
environment:
sdk: '>=2.12.0 <3.0.0'
dependencies:
crypto: ^3.0.1
platform_collections: ^8.0.0
platform_contracts: ^8.0.0
platform_macroable: ^8.0.0
platform_support: ^8.0.0
dev_dependencies:
test: ^1.16.0
lints: ^2.0.0

View file

@ -0,0 +1,72 @@
import 'package:test/test.dart';
import 'package:platform_cookie/src/cookie_jar.dart';
void main() {
group('CookieJar', () {
late CookieJar cookieJar;
setUp(() {
cookieJar = CookieJar();
});
test('make creates a cookie with default values', () {
final cookie = cookieJar.make('name', 'value');
expect(cookie['name'], equals('name'));
expect(cookie['value'], equals('value'));
expect(cookie['path'], equals('/'));
expect(cookie['secure'], equals('false'));
expect(cookie['httponly'], equals('true'));
});
test('make creates a cookie with custom values', () {
final cookie = cookieJar.make('name', 'value',
domain: 'example.com',
path: '/custom',
secure: true,
httpOnly: false,
sameSite: 'Strict');
expect(cookie['name'], equals('name'));
expect(cookie['value'], equals('value'));
expect(cookie['domain'], equals('example.com'));
expect(cookie['path'], equals('/custom'));
expect(cookie['secure'], equals('true'));
expect(cookie['httponly'], equals('false'));
expect(cookie['samesite'], equals('Strict'));
});
test('queue adds a cookie to the queue', () {
cookieJar.queue('name', 'value');
expect(cookieJar.hasQueued('name'), isTrue);
});
test('unqueue removes a cookie from the queue', () {
cookieJar.queue('name', 'value');
cookieJar.unqueue('name');
expect(cookieJar.hasQueued('name'), isFalse);
});
test('getQueuedCookies returns all queued cookies', () {
cookieJar.queue('name1', 'value1');
cookieJar.queue('name2', 'value2');
final queuedCookies = cookieJar.getQueuedCookies();
expect(queuedCookies.length, equals(2));
expect(queuedCookies['name1']?['value'], equals('value1'));
expect(queuedCookies['name2']?['value'], equals('value2'));
});
test('make encodes value when raw is false', () {
final cookie = cookieJar.make('name', 'value with spaces');
expect(cookie['value'], equals('value%20with%20spaces'));
});
test('make does not encode value when raw is true', () {
final cookie = cookieJar.make('name', 'value with spaces', raw: true);
expect(cookie['value'], equals('value with spaces'));
});
test('make sets expiration time when minutes are provided', () {
final cookie = cookieJar.make('name', 'value', minutes: 30);
expect(cookie['expires'], isNotEmpty);
});
});
}

View file

@ -0,0 +1,48 @@
import 'package:test/test.dart';
import 'package:platform_cookie/src/cookie_value_prefix.dart';
void main() {
group('CookieValuePrefix', () {
test('create returns a valid prefix', () {
final prefix = CookieValuePrefix.create('cookieName', 'secret');
expect(prefix, hasLength(41));
expect(prefix, endsWith('|'));
});
test('remove strips the prefix', () {
const value = 'prefixValue|actualValue';
expect(CookieValuePrefix.remove(value), equals('actualValue'));
});
test('validate returns the value without prefix for a valid cookie', () {
const cookieName = 'testCookie';
const key = 'secret';
final prefix = CookieValuePrefix.create(cookieName, key);
final cookieValue = '${prefix}actualValue';
final result = CookieValuePrefix.validate(cookieName, cookieValue, [key]);
expect(result, equals('actualValue'));
});
test('validate returns null for an invalid cookie', () {
const cookieName = 'testCookie';
const key = 'secret';
const cookieValue = 'invalidPrefix|actualValue';
final result = CookieValuePrefix.validate(cookieName, cookieValue, [key]);
expect(result, isNull);
});
test('validate checks multiple keys', () {
const cookieName = 'testCookie';
const key1 = 'secret1';
const key2 = 'secret2';
final prefix = CookieValuePrefix.create(cookieName, key2);
final cookieValue = '${prefix}actualValue';
final result =
CookieValuePrefix.validate(cookieName, cookieValue, [key1, key2]);
expect(result, equals('actualValue'));
});
});
}