summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore448
-rw-r--r--LICENSE24
-rw-r--r--LICENSE_NOTICES31
-rw-r--r--README.md0
-rw-r--r--src/IOL.Helpers.sln34
-rw-r--r--src/IOL.Helpers/CryptographyHelpers.cs173
-rw-r--r--src/IOL.Helpers/DateTimeHelpers.cs22
-rw-r--r--src/IOL.Helpers/DoubleHelpers.cs11
-rw-r--r--src/IOL.Helpers/EnumHelpers.cs13
-rw-r--r--src/IOL.Helpers/HttpRequestHelpers.cs18
-rw-r--r--src/IOL.Helpers/IOL.Helpers.csproj12
-rw-r--r--src/IOL.Helpers/PasswordHelpers.cs70
-rw-r--r--src/IOL.Helpers/RandomStringGenerator.cs18
-rw-r--r--src/IOL.Helpers/SlugGenerator.cs108
-rw-r--r--src/IOL.Helpers/StringHelpers.cs67
-rw-r--r--src/IOL.Helpers/Validators.cs18
16 files changed, 1067 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..de03f94
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,448 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# Tye
+.tye/
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*[.json, .xml, .info]
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# Ionide - VsCode extension for F# Support
+.ionide/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+##
+## Visual studio for Mac
+##
+
+
+# globs
+Makefile.in
+*.userprefs
+*.usertasks
+config.make
+config.status
+aclocal.m4
+install-sh
+autom4te.cache/
+*.tar.gz
+tarballs/
+test-results/
+
+# Mac bundle stuff
+*.dmg
+*.app
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+##
+## Visual Studio Code
+##
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..fdddb29
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+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 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.
+
+For more information, please refer to <https://unlicense.org>
diff --git a/LICENSE_NOTICES b/LICENSE_NOTICES
new file mode 100644
index 0000000..c37df05
--- /dev/null
+++ b/LICENSE_NOTICES
@@ -0,0 +1,31 @@
+IOL.VippsEcommerce uses third-party libraries or other resources that may
+be distributed under licenses different than the IOL.VippsEcommerce software.
+
+In the event that we accidentally failed to list a required notice,
+please bring it to our attention through any of the ways detailed here:
+
+ https://github.com/ivarlovlie/IOL.VippsEcommerce/issues
+
+The attached notices are provided for information only.
+
+For any licenses that require disclosure of source, sources are available at
+https://github.com/ivarlovlie/IOL.VippsEcommerce/.
+
+License Notice for Microsoft.NET.Sdk
+---------------------------
+https://github.com/dotnet/runtime/blob/main/LICENSE.TXT
+
+
+License Notice for Microsoft.Extensions.DependencyInjection
+---------------------------
+https://github.com/dotnet/runtime/blob/main/LICENSE.TXT
+
+
+License Notice for Microsoft.Extensions.Http
+---------------------------
+https://github.com/dotnet/runtime/blob/main/LICENSE.TXT
+
+
+License Notice for Microsoft.Extensions.Logging
+---------------------------
+https://github.com/dotnet/runtime/blob/main/LICENSE.TXT
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/README.md
diff --git a/src/IOL.Helpers.sln b/src/IOL.Helpers.sln
new file mode 100644
index 0000000..b166d6e
--- /dev/null
+++ b/src/IOL.Helpers.sln
@@ -0,0 +1,34 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.6.30114.105
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IOL.Helpers", "IOL.Helpers\IOL.Helpers.csproj", "{DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Debug|x64.Build.0 = Debug|Any CPU
+ {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Debug|x86.Build.0 = Debug|Any CPU
+ {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Release|x64.ActiveCfg = Release|Any CPU
+ {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Release|x64.Build.0 = Release|Any CPU
+ {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Release|x86.ActiveCfg = Release|Any CPU
+ {DE4D43B0-FBE6-40B7-9C8F-FFFA7987F87B}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/src/IOL.Helpers/CryptographyHelpers.cs b/src/IOL.Helpers/CryptographyHelpers.cs
new file mode 100644
index 0000000..6ea18b6
--- /dev/null
+++ b/src/IOL.Helpers/CryptographyHelpers.cs
@@ -0,0 +1,173 @@
+using System;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace IOL.Helpers
+{
+ public static class CryptographyHelpers
+ {
+ // https://github.com/DuendeSoftware/IdentityServer/blob/main/src/IdentityServer/Extensions/HashExtensions.cs
+
+ private const int AES_BLOCK_BYTE_SIZE = 128 / 8;
+ private static readonly RandomNumberGenerator _random = RandomNumberGenerator.Create();
+
+ /// <summary>
+ /// Creates a MD5 hash of the specified input.
+ /// </summary>
+ /// <returns>A hash</returns>
+ public static string Md5(this string input, string salt = default) {
+ if (input.IsNullOrWhiteSpace()) return string.Empty;
+
+ var hmacMd5 = salt.HasValue() ? new HMACMD5(Encoding.UTF8.GetBytes(salt ?? "")) : new HMACMD5();
+ var saltedHash = hmacMd5.ComputeHash(Encoding.UTF8.GetBytes(input));
+ return Convert.ToBase64String(saltedHash);
+ }
+
+
+ /// <summary>
+ /// Method to perform a very simple (and classical) encryption for a string. This is NOT at
+ /// all secure, it is only intended to make the string value non-obvious at a first glance.
+ ///
+ /// The shiftOrUnshift argument is an arbitrary "key value", and must be a non-zero integer
+ /// between -65535 and 65535 (inclusive). To decrypt the encrypted string you use the negative
+ /// value. For example, if you encrypt with -42, then you decrypt with +42, or vice-versa.
+ ///
+ /// This is inspired by, and largely based on, this:
+ /// https://stackoverflow.com/a/13026595/253938
+ /// </summary>
+ /// <param name="inputString">string to be encrypted or decrypted, must not be null</param>
+ /// <param name="shiftOrUnshift">see above</param>
+ /// <returns>encrypted or decrypted string</returns>
+ public static string CaesarCipher(string inputString, int shiftOrUnshift) {
+ const int C64_K = ushort.MaxValue + 1;
+ if (inputString == null) throw new ArgumentException("Must not be null.", nameof(inputString));
+ switch (shiftOrUnshift) {
+ case 0: throw new ArgumentException("Must not be zero.", nameof(shiftOrUnshift));
+ case <= -C64_K:
+ case >= C64_K: throw new ArgumentException("Out of range.", nameof(shiftOrUnshift));
+ }
+
+ // Perform the Caesar cipher shifting, using modulo operator to provide wrap-around
+ var charArray = new char[inputString.Length];
+ for (var i = 0; i < inputString.Length; i++) {
+ charArray[i] =
+ Convert.ToChar((Convert.ToInt32(inputString[i]) + shiftOrUnshift + C64_K) % C64_K);
+ }
+
+ return new string(charArray);
+ }
+
+ //https://tomrucki.com/posts/aes-encryption-in-csharp/
+ public static string EncryptWithAes(this string toEncrypt, string password) {
+ var key = GetKey(password);
+
+ using var aes = CreateAes();
+ var iv = GenerateRandomBytes(AES_BLOCK_BYTE_SIZE);
+ var plainText = Encoding.UTF8.GetBytes(toEncrypt);
+
+ using var encryptor = aes.CreateEncryptor(key, iv);
+ var cipherText = encryptor
+ .TransformFinalBlock(plainText, 0, plainText.Length);
+
+ var result = new byte[iv.Length + cipherText.Length];
+ iv.CopyTo(result, 0);
+ cipherText.CopyTo(result, iv.Length);
+
+ return Convert.ToBase64String(result);
+ }
+
+ private static Aes CreateAes() {
+ var aes = Aes.Create();
+ aes.Mode = CipherMode.CBC;
+ aes.Padding = PaddingMode.PKCS7;
+ return aes;
+ }
+
+ public static string DecryptWithAes(this string input, string password) {
+ var key = GetKey(password);
+ var encryptedData = Convert.FromBase64String(input);
+
+ using var aes = CreateAes();
+ var iv = encryptedData.Take(AES_BLOCK_BYTE_SIZE).ToArray();
+ var cipherText = encryptedData.Skip(AES_BLOCK_BYTE_SIZE).ToArray();
+
+ using var decryptor = aes.CreateDecryptor(key, iv);
+ var decryptedBytes = decryptor
+ .TransformFinalBlock(cipherText, 0, cipherText.Length);
+ return Encoding.UTF8.GetString(decryptedBytes);
+ }
+
+ private static byte[] GetKey(string password) {
+ var keyBytes = Encoding.UTF8.GetBytes(password);
+ using var md5 = MD5.Create();
+ return md5.ComputeHash(keyBytes);
+ }
+
+ private static byte[] GenerateRandomBytes(int numberOfBytes) {
+ var randomBytes = new byte[numberOfBytes];
+ _random.GetBytes(randomBytes);
+ return randomBytes;
+ }
+
+
+ /// <summary>
+ /// Creates a SHA256 hash of the specified input.
+ /// </summary>
+ /// <param name="input">The input.</param>
+ /// <returns>A hash</returns>
+ public static string Sha256(this string input) {
+ if (input.IsNullOrWhiteSpace()) return string.Empty;
+
+ using var sha = SHA256.Create();
+ var bytes = Encoding.UTF8.GetBytes(input);
+ var hash = sha.ComputeHash(bytes);
+
+ return Convert.ToBase64String(hash);
+ }
+
+ /// <summary>
+ /// Creates a SHA256 hash of the specified input.
+ /// </summary>
+ /// <param name="input">The input.</param>
+ /// <returns>A hash.</returns>
+ public static byte[] Sha256(this byte[] input) {
+ if (input == null) {
+ return null;
+ }
+
+ using var sha = SHA256.Create();
+ return sha.ComputeHash(input);
+ }
+
+ /// <summary>
+ /// Creates a SHA512 hash of the specified input.
+ /// </summary>
+ /// <param name="input">The input.</param>
+ /// <returns>A hash</returns>
+ public static string Sha512(this string input) {
+ if (input.IsNullOrWhiteSpace()) return string.Empty;
+
+ using var sha = SHA512.Create();
+ var bytes = Encoding.UTF8.GetBytes(input);
+ var hash = sha.ComputeHash(bytes);
+
+ return Convert.ToBase64String(hash);
+ }
+
+
+ /// <summary>
+ /// Creates a SHA256 hash of the specified input.
+ /// </summary>
+ /// <param name="input">The input.</param>
+ /// <returns>A hash.</returns>
+ public static byte[] Sha512(this byte[] input) {
+ if (input == null) {
+ return null;
+ }
+
+ using var sha = SHA512.Create();
+ return sha.ComputeHash(input);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.Helpers/DateTimeHelpers.cs b/src/IOL.Helpers/DateTimeHelpers.cs
new file mode 100644
index 0000000..07e951e
--- /dev/null
+++ b/src/IOL.Helpers/DateTimeHelpers.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace IOL.Helpers
+{
+ public static class DateTimeHelpers
+ {
+ public static DateTime ToTimeZoneId(this DateTime value, string timeZoneId) {
+ try {
+ var cstZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
+ return TimeZoneInfo.ConvertTimeFromUtc(value, cstZone);
+ } catch (TimeZoneNotFoundException) {
+ Console.WriteLine("The registry does not define the " + timeZoneId + " zone.");
+ return default;
+ } catch (InvalidTimeZoneException) {
+ Console.WriteLine("Registry data on the " + timeZoneId + " zone has been corrupted.");
+ return default;
+ }
+ }
+
+ public static DateTime ToOsloTimeZone(this DateTime value) => ToTimeZoneId(value, "Europe/Oslo");
+ }
+} \ No newline at end of file
diff --git a/src/IOL.Helpers/DoubleHelpers.cs b/src/IOL.Helpers/DoubleHelpers.cs
new file mode 100644
index 0000000..91d88ee
--- /dev/null
+++ b/src/IOL.Helpers/DoubleHelpers.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace IOL.Helpers
+{
+ public static class DoubleHelpers
+ {
+ public static string ToStringWithFixedDecimalPoints(this double value) {
+ return $"{Math.Truncate(value * 10) / 10:0.0}";
+ }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.Helpers/EnumHelpers.cs b/src/IOL.Helpers/EnumHelpers.cs
new file mode 100644
index 0000000..b63e045
--- /dev/null
+++ b/src/IOL.Helpers/EnumHelpers.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace IOL.Helpers
+{
+ public static class EnumHelpers
+ {
+ public static IEnumerable<T> GetValues<T>() {
+ return Enum.GetValues(typeof(T)).Cast<T>();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.Helpers/HttpRequestHelpers.cs b/src/IOL.Helpers/HttpRequestHelpers.cs
new file mode 100644
index 0000000..5a0e145
--- /dev/null
+++ b/src/IOL.Helpers/HttpRequestHelpers.cs
@@ -0,0 +1,18 @@
+using System;
+using Microsoft.AspNetCore.Http;
+
+namespace IOL.Helpers
+{
+ public static class HttpRequestHelpers
+ {
+ public static string GetAppHost(this HttpRequest request) {
+ var forwardedHostHeader = request.Headers["X-Forwarded-Host"].ToString();
+ var forwardedProtoHeader = request.Headers["X-Forwarded-Proto"].ToString();
+ if (forwardedHostHeader.HasValue()) {
+ return (forwardedProtoHeader ?? "https") + "://" + forwardedHostHeader;
+ }
+
+ return request.Scheme + "://" + request.Host;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.Helpers/IOL.Helpers.csproj b/src/IOL.Helpers/IOL.Helpers.csproj
new file mode 100644
index 0000000..2b2842a
--- /dev/null
+++ b/src/IOL.Helpers/IOL.Helpers.csproj
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net5.0</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="5.0.4" />
+ <PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
+ </ItemGroup>
+
+</Project>
diff --git a/src/IOL.Helpers/PasswordHelpers.cs b/src/IOL.Helpers/PasswordHelpers.cs
new file mode 100644
index 0000000..5b85219
--- /dev/null
+++ b/src/IOL.Helpers/PasswordHelpers.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.Security.Cryptography;
+using Microsoft.AspNetCore.Cryptography.KeyDerivation;
+
+namespace IOL.Helpers
+{
+ public static class PasswordHelper
+ {
+ private const int ITERATION_COUNT = 10000;
+ private const int SALT_SIZE = 128 / 8;
+ private const KeyDerivationPrf PRF = KeyDerivationPrf.HMACSHA256;
+
+ public static string HashPassword(string value) {
+ using var rng = RandomNumberGenerator.Create();
+ var salt = new byte[SALT_SIZE];
+ rng.GetBytes(salt);
+ var subkey = KeyDerivation.Pbkdf2(value, salt, PRF, ITERATION_COUNT, 256 / 8);
+ var outputBytes = new byte[13 + salt.Length + subkey.Length];
+ WriteNetworkByteOrder(outputBytes, 1, (uint) PRF);
+ WriteNetworkByteOrder(outputBytes, 5, (uint) ITERATION_COUNT);
+ WriteNetworkByteOrder(outputBytes, 9, (uint) SALT_SIZE);
+ Buffer.BlockCopy(salt, 0, outputBytes, 13, salt.Length);
+ Buffer.BlockCopy(subkey, 0, outputBytes, 13 + SALT_SIZE, subkey.Length);
+ return Convert.ToBase64String(outputBytes);
+ }
+
+ public static bool Verify(string password, string hashedPassword) {
+ var decodedHashedPassword = Convert.FromBase64String(hashedPassword);
+ if (decodedHashedPassword.Length == 0) return false;
+ try {
+ // Read header information
+ var networkByteOrder = (KeyDerivationPrf) ReadNetworkByteOrder(decodedHashedPassword, 1);
+ var saltLength = (int) ReadNetworkByteOrder(decodedHashedPassword, 9);
+
+ // Read the salt: must be >= 128 bits
+ if (saltLength < SALT_SIZE) return false;
+ var salt = new byte[saltLength];
+ Buffer.BlockCopy(decodedHashedPassword, 13, salt, 0, salt.Length);
+
+ // Read the subkey (the rest of the payload): must be >= 128 bits
+ var subkeyLength = decodedHashedPassword.Length - 13 - salt.Length;
+ if (subkeyLength < SALT_SIZE) return false;
+ var expectedSubkey = new byte[subkeyLength];
+ Buffer.BlockCopy(decodedHashedPassword, 13 + salt.Length, expectedSubkey, 0, expectedSubkey.Length);
+
+ // Hash the incoming password and verify it
+ var actualSubkey =
+ KeyDerivation.Pbkdf2(password, salt, networkByteOrder, ITERATION_COUNT, subkeyLength);
+ return CryptographicOperations.FixedTimeEquals(actualSubkey, expectedSubkey);
+ } catch {
+ return false;
+ }
+ }
+
+ private static uint ReadNetworkByteOrder(IReadOnlyList<byte> buffer, int offset) {
+ return ((uint) buffer[offset + 0] << 24)
+ | ((uint) buffer[offset + 1] << 16)
+ | ((uint) buffer[offset + 2] << 8)
+ | buffer[offset + 3];
+ }
+
+ private static void WriteNetworkByteOrder(IList<byte> buffer, int offset, uint value) {
+ buffer[offset + 0] = (byte) (value >> 24);
+ buffer[offset + 1] = (byte) (value >> 16);
+ buffer[offset + 2] = (byte) (value >> 8);
+ buffer[offset + 3] = (byte) (value >> 0);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.Helpers/RandomStringGenerator.cs b/src/IOL.Helpers/RandomStringGenerator.cs
new file mode 100644
index 0000000..0d691db
--- /dev/null
+++ b/src/IOL.Helpers/RandomStringGenerator.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Linq;
+
+namespace IOL.Helpers
+{
+ public static class RandomString
+ {
+ private static readonly Random _random = new Random();
+
+ public static string Generate(int length, bool numeric = false) {
+ var chars = numeric switch {
+ false => "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
+ true => "0123456789"
+ };
+ return new string(Enumerable.Repeat(chars, length).Select(s => s[_random.Next(s.Length)]).ToArray());
+ }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.Helpers/SlugGenerator.cs b/src/IOL.Helpers/SlugGenerator.cs
new file mode 100644
index 0000000..3f53dd6
--- /dev/null
+++ b/src/IOL.Helpers/SlugGenerator.cs
@@ -0,0 +1,108 @@
+using System.Text;
+
+namespace IOL.Helpers
+{
+ public static class Slug
+ {
+ public static string Generate(bool toLower, params string[] values) {
+ return Create(string.Join("-", values), toLower);
+ }
+
+ /// <summary>
+ /// Creates a slug.
+ /// References:
+ /// http://www.unicode.org/reports/tr15/tr15-34.html
+ /// https://meta.stackexchange.com/questions/7435/non-us-ascii-characters-dropped-from-full-profile-url/7696#7696
+ /// https://stackoverflow.com/questions/25259/how-do-you-include-a-webpage-title-as-part-of-a-webpage-url/25486#25486
+ /// https://stackoverflow.com/questions/3769457/how-can-i-remove-accents-on-a-string
+ /// </summary>
+ /// <param name="value"></param>
+ /// <param name="toLower"></param>
+ /// <returns>Slugified string</returns>
+ public static string Create(string value, bool toLower) {
+ if (string.IsNullOrWhiteSpace(value))
+ return value;
+
+ var normalised = value.Normalize(NormalizationForm.FormKD);
+
+ const int MAXLEN = 80;
+ var len = normalised.Length;
+ var prevDash = false;
+ var sb = new StringBuilder(len);
+
+ for (var i = 0; i < len; i++) {
+ var c = normalised[i];
+ switch (c) {
+ case >= 'a' and <= 'z':
+ case >= '0' and <= '9': {
+ if (prevDash) {
+ sb.Append('-');
+ prevDash = false;
+ }
+
+ sb.Append(c);
+ break;
+ }
+ case >= 'A' and <= 'Z': {
+ if (prevDash) {
+ sb.Append('-');
+ prevDash = false;
+ }
+
+ // Tricky way to convert to lowercase
+ if (toLower)
+ sb.Append((char) (c | 32));
+ else
+ sb.Append(c);
+ break;
+ }
+ case ' ':
+ case ',':
+ case '.':
+ case '/':
+ case '\\':
+ case '-':
+ case '_':
+ case '=': {
+ if (!prevDash && sb.Length > 0) {
+ prevDash = true;
+ }
+
+ break;
+ }
+ default: {
+ var swap = ConvertEdgeCases(c, toLower);
+ if (swap != null) {
+ if (prevDash) {
+ sb.Append('-');
+ prevDash = false;
+ }
+
+ sb.Append(swap);
+ }
+
+ break;
+ }
+ }
+
+ if (sb.Length == MAXLEN)
+ break;
+ }
+
+ return sb.ToString();
+ }
+
+ private static string ConvertEdgeCases(char c, bool toLower) => c switch {
+ 'ı' => "i",
+ 'ł' => "l",
+ 'Ł' => toLower ? "l" : "L",
+ 'đ' => "d",
+ 'ß' => "ss",
+ 'ø' => "o",
+ 'å' => "aa",
+ 'æ' => "ae",
+ 'Þ' => "th",
+ _ => null
+ };
+ }
+} \ No newline at end of file
diff --git a/src/IOL.Helpers/StringHelpers.cs b/src/IOL.Helpers/StringHelpers.cs
new file mode 100644
index 0000000..1dde161
--- /dev/null
+++ b/src/IOL.Helpers/StringHelpers.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace IOL.Helpers
+{
+ public static class StringHelpers
+ {
+ public static bool IsNullOrWhiteSpace(this string value) {
+ return string.IsNullOrWhiteSpace(value);
+ }
+
+ public static string Slugified(this string input) {
+ return Slug.Generate(true, input);
+ }
+
+ public static bool HasValue(this string value) {
+ return !value.IsNullOrWhiteSpace();
+ }
+
+ public static Guid ToGuid(this string value) {
+ return Guid.Parse(value);
+ }
+
+ public static string Base64Encode(this string text) {
+ return Convert.ToBase64String(Encoding.UTF8.GetBytes(text));
+ }
+
+ public static string ExtractFileName(this string value) {
+ if (value.IsNullOrWhiteSpace()) return default;
+ var lastIndex = value.LastIndexOf('.');
+ return lastIndex <= 0 ? default : value.Substring(0, lastIndex);
+ }
+
+ public static string ExtractExtension(this string value) {
+ if (value.IsNullOrWhiteSpace()) return default;
+ var lastIndex = value.LastIndexOf('.');
+ return lastIndex <= 0 ? default : value.Substring(lastIndex);
+ }
+
+ public static string Capitalize(this string input) {
+ return input.IsNullOrWhiteSpace()
+ ? input
+ : Regex.Replace(input, @"\b(\w)", m => m.Value.ToUpper(), RegexOptions.None);
+ }
+
+
+ /// <summary>
+ /// Check if the given MIME is a JSON MIME.
+ /// </summary>
+ /// <param name="mime">MIME</param>
+ /// <returns>Returns true if MIME type is json.</returns>
+ public static bool IsJsonMime(this string mime) {
+ var jsonRegex = new Regex("(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$");
+ return mime != null && (jsonRegex.IsMatch(mime) || mime.Equals("application/json-patch+json"));
+ }
+
+ public static string Obfuscate(this string value) {
+ var last4Chars = "****";
+ if (value.HasValue() && value.Length > 4) {
+ last4Chars = value.Substring(value.Length - 4);
+ }
+
+ return "****" + last4Chars;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/IOL.Helpers/Validators.cs b/src/IOL.Helpers/Validators.cs
new file mode 100644
index 0000000..d60afcf
--- /dev/null
+++ b/src/IOL.Helpers/Validators.cs
@@ -0,0 +1,18 @@
+using System.Net.Mail;
+using System.Text.RegularExpressions;
+
+namespace IOL.Helpers
+{
+ public static class Validators
+ {
+ private static readonly Regex _norwegianPhoneNumber = new(@"^(0047|\+47|47)?[2-9]\d{7}$");
+
+ public static bool IsValidEmailAddress(this string value) {
+ return MailAddress.TryCreate(value, out _);
+ }
+
+ public static bool IsValidNorwegianPhoneNumber(this string value) {
+ return _norwegianPhoneNumber.IsMatch(value);
+ }
+ }
+} \ No newline at end of file