cyrtophora

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 21d82c8b7c83fd7e70caa8f05873d456a6e7e4b6
parent 97188539648fba10f120be9ef12ae3cb182aa1e2
Author: seestem <seestem@noreply.codeberg.org>
Date:   Wed, 31 Aug 2022 16:55:31 +0200

Merge pull request 'seting up client (cyrto)' (#3) from dev into root

Reviewed-on: https://codeberg.org/seestem/cyrtophora/pulls/3

Diffstat:
Acyrto/.gitignore | 10++++++++++
Acyrto/CHANGELOG.md | 3+++
Acyrto/README.md | 1+
Acyrto/analysis_options.yaml | 30++++++++++++++++++++++++++++++
Acyrto/example/cyrtophora_example.dart | 6++++++
Acyrto/lib/cyrtophora.dart | 9+++++++++
Acyrto/lib/src/account.dart | 10++++++++++
Acyrto/lib/src/api.dart | 30++++++++++++++++++++++++++++++
Acyrto/lib/src/data.dart | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acyrto/lib/src/utils.dart | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acyrto/lib/src/validate.dart | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acyrto/pubspec.yaml | 14++++++++++++++
Acyrto/test/cyrtophora_test.dart | 5+++++
Acyrto/test/utils_test.dart | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acyrto/test/validate_test.dart | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mphora/Cargo.lock | 21+++++++++++++++++++++
Mphora/Cargo.toml | 5+++--
Mphora/src/account.rs | 56++++++++++++++++++++++++++++----------------------------
Aphora/src/api.rs | 13+++++++++++++
Mphora/src/crypto.rs | 3---
Aphora/src/data.rs | 35+++++++++++++++++++++++++++++++++++
Mphora/src/lib.rs | 8+++++---
22 files changed, 608 insertions(+), 36 deletions(-)

diff --git a/cyrto/.gitignore b/cyrto/.gitignore @@ -0,0 +1,10 @@ +# Files and directories created by pub. +.dart_tool/ +.packages + +# Conventional directory for build outputs. +build/ + +# Omit committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/cyrto/CHANGELOG.md b/cyrto/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/cyrto/README.md b/cyrto/README.md @@ -0,0 +1 @@ +# cyrtophora client diff --git a/cyrto/analysis_options.yaml b/cyrto/analysis_options.yaml @@ -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 diff --git a/cyrto/example/cyrtophora_example.dart b/cyrto/example/cyrtophora_example.dart @@ -0,0 +1,6 @@ +import 'package:cyrtophora/cyrtophora.dart'; + +void main() { + var awesome = Awesome(); + print('awesome: ${awesome.isAwesome}'); +} diff --git a/cyrto/lib/cyrtophora.dart b/cyrto/lib/cyrtophora.dart @@ -0,0 +1,9 @@ +/// cyrtophora client + +library cyrtophora; + +export 'src/utils.dart'; +export 'src/validate.dart'; +export 'src/data.dart'; +export 'src/api.dart'; +export 'src/account.dart'; diff --git a/cyrto/lib/src/account.dart b/cyrto/lib/src/account.dart @@ -0,0 +1,10 @@ +/// Account Public Data +class PublicAccount { + String username; + PublicAccount(this.username); + + /// Generate input from JSON + factory PublicAccount.fromJson(dynamic json) { + return PublicAccount(json['username'] as String); + } +} diff --git a/cyrto/lib/src/api.dart b/cyrto/lib/src/api.dart @@ -0,0 +1,30 @@ +import 'package:http/http.dart' as http; +import 'package:cyrtophora/src/data.dart'; +import 'dart:convert'; +import 'package:cyrtophora/src/account.dart'; + +/// Accounts API +class CyrtophoraAPI { + CyrtophoraAPI(this.api); + final String api; + final Map<String, String> jsonHeaders = { + 'Content-Type': 'application/json; charset=UTF-8', + }; + + /// Create new account + Future<PublicAccount> create(AccountRegistration input) async { + final response = await http.post( + Uri.parse("$api/accounts"), + headers: jsonHeaders, + body: jsonEncode(input.toJson()), + ); + + if (response.statusCode == 400) { + throw Exception('Invalid Input: ${response.body}'); + } else if (response.statusCode == 201) { + return PublicAccount.fromJson(jsonDecode(response.body)); + } else { + throw Exception('Server side error: ${response.body}'); + } + } +} diff --git a/cyrto/lib/src/data.dart b/cyrto/lib/src/data.dart @@ -0,0 +1,64 @@ +import "package:cyrtophora/cyrtophora.dart"; + +/// Account registration input +class AccountRegistration { + String username; + String email; + String password; + String retyped_password; + + AccountRegistration( + this.username, this.email, this.password, this.retyped_password); + + /// Generate input from JSON + factory AccountRegistration.fromJson(dynamic json) { + return AccountRegistration( + json['username'] as String, + json['email'] as String, + json['password'] as String, + json['retyped_password'] as String); + } + + /// Serialize object to JSON + Map<String, String> toJson() { + return { + 'username': username, + 'email': email, + 'password': password, + 'retyped_password': retyped_password + }; + } + + /// Validate input and create + bool isValid() { + // Check if password and retyped password match + if (password != retyped_password) { + throw Exception('InvalidPasswordException\n\n- passwords do not match'); + } + // Validate username + if (!Validate.username(username)) { + throw Exception( + 'InvalidUsernameException\n\n- Username cannot be empty\n- Username cannot be more than 15 characters long\n- Username cannot contain symbols expcept one _'); + } + + // Validate fullname + // if (!Validate.fullname(fullname)) { + // //throw InvalidNameException; + // throw Exception( + // 'InvalidNameException\n\n- Name cannot be empty\n- Name cannot contain more than 70 characters'); + // } + + // Validate password + if (!Validate.password(password)) { + throw Exception( + 'InvalidPasswordException\n\n- Password length should be 10 chartcers or more'); + } + + // Validate email + if (!Validate.email(email)) { + throw Exception('InvalidEmailException\n\n- Invalid email'); + } + + return true; + } +} diff --git a/cyrto/lib/src/utils.dart b/cyrto/lib/src/utils.dart @@ -0,0 +1,75 @@ +/// utilities -- cyrtophora utility functions + +/// Check if a character is alphanumeric +bool isAlphanumeric(String char) { + final alphanumeric = [ + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "0" + ]; + + if (alphanumeric.contains(char)) { + return true; + } else { + return false; + } +} diff --git a/cyrto/lib/src/validate.dart b/cyrto/lib/src/validate.dart @@ -0,0 +1,80 @@ +import "package:cyrtophora/cyrtophora.dart"; + +class Validate { + /// Validate username + static bool username(String username) { + // Username cannot be empty + if (username.isEmpty) { + return false; + } + + // Username length cannot be greater than 15 characters + if (username.length > 15) { + return false; + } + + final chars = username.split(""); + var underscoreCount = 0; + + for (var i = 0; i < chars.length; i++) { + // Username cannot start with a underscore (_) + if (i == 0 && chars[i] == '_') { + return false; + } + + // Username can only contain letters, numbers, one dot and one underscore + if (chars[i] != '_') { + if (!isAlphanumeric(chars[i])) { + return false; + } + } else { + // Count underscores because name can have only one [ _ ] underscore + underscoreCount += 1; + } + } + // Username can have only one [ _ ] underscore + if (underscoreCount > 1) { + return false; + } + return true; + } + + /// Validate fullname if it is provided + static bool fullname(String fullname) { + // Full Name cannot be empty + if (fullname.isEmpty) { + return false; + } + + // Full Name cannot be more than 70 characters + if (fullname.length > 70) { + return false; + } + + return true; + } + + /// Validate password + static bool password(String password) { + // Password should be at least 10 characters long + if (password.length < 10) { + return false; + } + return true; + } + + // TODO: better email verification + /// Validate email address + static bool email(String email) { + // Email address cannot be empty + if (email.isEmpty) { + return false; + } + + // Email address should contain @ and . symbols + if (!email.contains('@') || !email.contains('.')) { + return false; + } + return true; + } +} diff --git a/cyrto/pubspec.yaml b/cyrto/pubspec.yaml @@ -0,0 +1,14 @@ +name: cyrtophora +description: A starting point for Dart libraries or applications. +version: 1.0.0 +# homepage: https://www.example.com + +environment: + sdk: '>=2.17.0-227.0.dev <3.0.0' + +dependencies: + http: ^0.13.5 + +dev_dependencies: + lints: ^2.0.0 + test: ^1.16.0 diff --git a/cyrto/test/cyrtophora_test.dart b/cyrto/test/cyrtophora_test.dart @@ -0,0 +1,5 @@ +import 'package:test/test.dart'; + +void main() { + group('Tests for cyrtophora utilities', () {}); +} diff --git a/cyrto/test/utils_test.dart b/cyrto/test/utils_test.dart @@ -0,0 +1,100 @@ +import 'package:cyrtophora/cyrtophora.dart'; +import 'package:test/test.dart'; + +void main() { + group('Tests for cyrtophora utilities', () { + test('Test isAlphaNumeric function', () { + expect(isAlphanumeric("a"), isTrue); + expect(isAlphanumeric("b"), isTrue); + expect(isAlphanumeric("c"), isTrue); + expect(isAlphanumeric("d"), isTrue); + expect(isAlphanumeric("e"), isTrue); + expect(isAlphanumeric("f"), isTrue); + expect(isAlphanumeric("g"), isTrue); + expect(isAlphanumeric("h"), isTrue); + expect(isAlphanumeric("i"), isTrue); + expect(isAlphanumeric("j"), isTrue); + expect(isAlphanumeric("k"), isTrue); + expect(isAlphanumeric("l"), isTrue); + expect(isAlphanumeric("m"), isTrue); + expect(isAlphanumeric("n"), isTrue); + expect(isAlphanumeric("o"), isTrue); + expect(isAlphanumeric("p"), isTrue); + expect(isAlphanumeric("q"), isTrue); + expect(isAlphanumeric("r"), isTrue); + expect(isAlphanumeric("s"), isTrue); + expect(isAlphanumeric("t"), isTrue); + expect(isAlphanumeric("u"), isTrue); + expect(isAlphanumeric("v"), isTrue); + expect(isAlphanumeric("w"), isTrue); + expect(isAlphanumeric("x"), isTrue); + expect(isAlphanumeric("y"), isTrue); + expect(isAlphanumeric("z"), isTrue); + expect(isAlphanumeric("A"), isTrue); + expect(isAlphanumeric("B"), isTrue); + expect(isAlphanumeric("C"), isTrue); + expect(isAlphanumeric("D"), isTrue); + expect(isAlphanumeric("E"), isTrue); + expect(isAlphanumeric("F"), isTrue); + expect(isAlphanumeric("G"), isTrue); + expect(isAlphanumeric("H"), isTrue); + expect(isAlphanumeric("I"), isTrue); + expect(isAlphanumeric("J"), isTrue); + expect(isAlphanumeric("K"), isTrue); + expect(isAlphanumeric("L"), isTrue); + expect(isAlphanumeric("M"), isTrue); + expect(isAlphanumeric("N"), isTrue); + expect(isAlphanumeric("O"), isTrue); + expect(isAlphanumeric("P"), isTrue); + expect(isAlphanumeric("Q"), isTrue); + expect(isAlphanumeric("R"), isTrue); + expect(isAlphanumeric("S"), isTrue); + expect(isAlphanumeric("T"), isTrue); + expect(isAlphanumeric("U"), isTrue); + expect(isAlphanumeric("V"), isTrue); + expect(isAlphanumeric("W"), isTrue); + expect(isAlphanumeric("X"), isTrue); + expect(isAlphanumeric("Y"), isTrue); + expect(isAlphanumeric("Z"), isTrue); + expect(isAlphanumeric("1"), isTrue); + expect(isAlphanumeric("2"), isTrue); + expect(isAlphanumeric("3"), isTrue); + expect(isAlphanumeric("4"), isTrue); + expect(isAlphanumeric("5"), isTrue); + expect(isAlphanumeric("6"), isTrue); + expect(isAlphanumeric("7"), isTrue); + expect(isAlphanumeric("8"), isTrue); + expect(isAlphanumeric("9"), isTrue); + expect(isAlphanumeric("0"), isTrue); + + expect(isAlphanumeric("!"), isFalse); + expect(isAlphanumeric("@"), isFalse); + expect(isAlphanumeric("#"), isFalse); + expect(isAlphanumeric("\$"), isFalse); + expect(isAlphanumeric("%"), isFalse); + expect(isAlphanumeric("^"), isFalse); + expect(isAlphanumeric("&"), isFalse); + expect(isAlphanumeric("*"), isFalse); + expect(isAlphanumeric("("), isFalse); + expect(isAlphanumeric(")"), isFalse); + expect(isAlphanumeric("-"), isFalse); + expect(isAlphanumeric("="), isFalse); + expect(isAlphanumeric("+"), isFalse); + expect(isAlphanumeric("\\"), isFalse); + expect(isAlphanumeric("]"), isFalse); + expect(isAlphanumeric("}"), isFalse); + expect(isAlphanumeric("["), isFalse); + expect(isAlphanumeric("{"), isFalse); + expect(isAlphanumeric(":"), isFalse); + expect(isAlphanumeric(";"), isFalse); + expect(isAlphanumeric("'"), isFalse); + expect(isAlphanumeric("\""), isFalse); + expect(isAlphanumeric("<"), isFalse); + expect(isAlphanumeric(">"), isFalse); + expect(isAlphanumeric("/"), isFalse); + expect(isAlphanumeric("?"), isFalse); + expect(isAlphanumeric("."), isFalse); + expect(isAlphanumeric(","), isFalse); + }); + }); +} diff --git a/cyrto/test/validate_test.dart b/cyrto/test/validate_test.dart @@ -0,0 +1,66 @@ +import 'package:test/test.dart'; +import 'package:cyrtophora/cyrtophora.dart'; + +void main() { + group('Test cyrtophora validator', () { + test('Test fullname validator', () { + final validName1 = "John Doe"; + final validName2 = "Peter Bob Bunny Wailer"; + final invalidName1 = + "kdfjafdjkdf dfjfdkdfjdf fdkjfdkjf dkfdfjdkfj dfdfjdfdkjf dfkjfdjdf fdkjfdjdkf fdnfdkjfd dffd dffdkfd fkdjfdj fdfdfdfdffdfdf dfkjf"; + final invalidName2 = ""; + + expect(Validate.fullname(validName1), true); + expect(Validate.fullname(validName2), true); + + // Name cannot be more than 70 characters long + expect(Validate.fullname(invalidName1), false); + + // Name cannot be empty + expect(Validate.fullname(invalidName2), false); + }); + + test('Test username validator', () { + final validUsername1 = "cy6erlion"; + final validUsername2 = "cy6er_lion"; + final validUsername3 = "cy6er1ion2022"; + final validUsername4 = "fishcanyon10"; + + final invalidUsername1 = "_cy6erlion"; + final invalidUsername2 = "cy6er.lion"; + final invalidUsername3 = "cy6er__lion"; + final invalidUsername4 = + "cy6erlion1111111111111111111111111eweewewewewew"; + final invalidUsername5 = ""; + + expect(Validate.username(validUsername1), true); + expect(Validate.username(validUsername2), true); + expect(Validate.username(validUsername3), true); + expect(Validate.username(validUsername4), true); + + expect(Validate.username(invalidUsername1), false); + expect(Validate.username(invalidUsername2), false); + expect(Validate.username(invalidUsername3), false); + expect(Validate.username(invalidUsername4), false); + expect(Validate.username(invalidUsername5), false); + }); + + test('Test password validator', () { + final validPassword = "thisIs_a_P@ssword"; + final invalidPassword = "short"; + + expect(Validate.password(validPassword), true); + expect(Validate.password(invalidPassword), false); + }); + + test('Test email validator', () { + final validEmail = "info@example.com"; + final invalidEmail1 = "wrong"; + final invalidEmail2 = ""; + + expect(Validate.email(validEmail), true); + expect(Validate.email(invalidEmail1), false); + expect(Validate.email(invalidEmail2), false); + }); + }); +} diff --git a/phora/Cargo.lock b/phora/Cargo.lock @@ -128,6 +128,7 @@ dependencies = [ "chacha20poly1305", "ring", "scrypt", + "serde", ] [[package]] @@ -312,6 +313,26 @@ dependencies = [ ] [[package]] +name = "serde" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "sha2" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/phora/Cargo.toml b/phora/Cargo.toml @@ -8,4 +8,5 @@ anyhow = "1.0.57" ring = "0.16.20" chacha20poly1305 = "0.9.0" base64 = "0.13.0" -scrypt = "0.10.0" -\ No newline at end of file +scrypt = "0.10.0" +serde = { version = "1.0.144", features = ["derive"] } +\ No newline at end of file diff --git a/phora/src/account.rs b/phora/src/account.rs @@ -1,6 +1,9 @@ -use crate::validate::{Validate, ValidationError}; +use crate::data::AccountRegistration; +use crate::validate::ValidationError; +use serde::{Deserialize, Serialize}; /// An account +#[derive(Deserialize, Serialize)] pub struct Account { /// The username of the user, also used as an unique identifier pub username: String, @@ -11,37 +14,34 @@ pub struct Account { } impl Account { - /// Register new user - pub fn register( - username: &str, - password: &str, - email: Option<&str>, - ) -> Result<Self, ValidationError> { - let mut validated_email: Option<String> = None; + /// Create new account + pub fn create(payload: AccountRegistration) -> Result<PublicAccount, ValidationError> { + payload.is_valid()?; - if !Validate::username(username) { - return Err(ValidationError::Username); - } - - if !Validate::password(password) { - return Err(ValidationError::Email); - } + // TODO: email verification + // TODO: database registration - if let Some(email) = email { - if !Validate::email(email) { - return Err(ValidationError::Email); - } - validated_email = Some(email.to_string()); + let account = Account { + username: payload.username, + email: payload.email, + password: payload.password, + }; - // TODO: email verification code - } + Ok(account.into()) + } +} - // TODO: database registration code +/// Account Public Data +#[derive(Deserialize, Serialize)] +pub struct PublicAccount { + /// The username of the user, also used as an unique identifier + pub username: String, +} - Ok(Account { - username: username.to_string(), - email: validated_email, - password: password.to_string(), - }) +impl From<Account> for PublicAccount { + fn from(account: Account) -> Self { + PublicAccount { + username: account.username, + } } } diff --git a/phora/src/api.rs b/phora/src/api.rs @@ -0,0 +1,13 @@ +pub struct API { + pub version: String, + pub accounts: String, +} + +impl Default for API { + fn default() -> Self { + API { + version: "/v0".to_string(), + accounts: "/v0/accounts".to_string(), + } + } +} diff --git a/phora/src/crypto.rs b/phora/src/crypto.rs @@ -13,11 +13,8 @@ use scrypt::{ Scrypt, }; -const CREDENTIAL_LEN: usize = digest::SHA256_OUTPUT_LEN; - /// Nonce used when encrypting the Crypto's keypair (Crypto::encrypt_keypair_document()) const AED_NONCE: &str = "people the information "; -pub type Credential = [u8; CREDENTIAL_LEN]; pub struct Credentials { /// Base64 encoded key pair serialized as a PKCS#8 document diff --git a/phora/src/data.rs b/phora/src/data.rs @@ -0,0 +1,35 @@ +use crate::validate::{Validate, ValidationError}; +use serde::{Deserialize, Serialize}; + +/// User registration Data +#[derive(Deserialize, Serialize)] +pub struct AccountRegistration { + pub username: String, + pub password: String, + pub retyped_password: String, + pub email: Option<String>, +} + +impl AccountRegistration { + pub fn is_valid(&self) -> Result<(), ValidationError> { + if self.password != self.retyped_password { + return Err(ValidationError::Password); + } + + if !Validate::username(&self.username) { + return Err(ValidationError::Username); + } + + if !Validate::password(&self.password) { + return Err(ValidationError::Password); + } + + if let Some(email) = &self.email { + if !Validate::email(email) { + return Err(ValidationError::Email); + } + } + + Ok(()) + } +} diff --git a/phora/src/lib.rs b/phora/src/lib.rs @@ -1,3 +1,5 @@ -mod account; -mod crypto; -mod validate; +pub mod account; +pub mod api; +pub mod crypto; +pub mod data; +pub mod validate;