Options
+@{
+ string name = null;
+ if (!true.Equals(ViewData["signed-out"]))
+ {
+ name = Context.User?.GetDisplayName();
+ }
+}
+
+
+
+
+
+
+ @Options.Value.Name
+
+
+
+
+
+
+
+
+
+
+ @if (!string.IsNullOrWhiteSpace(name))
+ {
+
+ }
+
+
+
+
+ @RenderBody()
+
+
+
+
+ @RenderSection("scripts", required: false)
+
+
diff --git a/TheIdServer/src/TIS/Views/Shared/_LoginPartial.cshtml b/TheIdServer.Duende/src/TIS/Views/Shared/_LoginPartial.cshtml
similarity index 100%
rename from TheIdServer/src/TIS/Views/Shared/_LoginPartial.cshtml
rename to TheIdServer.Duende/src/TIS/Views/Shared/_LoginPartial.cshtml
diff --git a/TheIdServer/src/TIS/Views/Shared/_ScopeListItem.cshtml b/TheIdServer.Duende/src/TIS/Views/Shared/_ScopeListItem.cshtml
similarity index 100%
rename from TheIdServer/src/TIS/Views/Shared/_ScopeListItem.cshtml
rename to TheIdServer.Duende/src/TIS/Views/Shared/_ScopeListItem.cshtml
diff --git a/TheIdServer/src/TIS/Views/Shared/_ValidationSummary.cshtml b/TheIdServer.Duende/src/TIS/Views/Shared/_ValidationSummary.cshtml
similarity index 100%
rename from TheIdServer/src/TIS/Views/Shared/_ValidationSummary.cshtml
rename to TheIdServer.Duende/src/TIS/Views/Shared/_ValidationSummary.cshtml
diff --git a/TheIdServer/src/TIS/Views/_ViewImports.cshtml b/TheIdServer.Duende/src/TIS/Views/_ViewImports.cshtml
similarity index 100%
rename from TheIdServer/src/TIS/Views/_ViewImports.cshtml
rename to TheIdServer.Duende/src/TIS/Views/_ViewImports.cshtml
diff --git a/TheIdServer/src/TIS/Views/_ViewStart.cshtml b/TheIdServer.Duende/src/TIS/Views/_ViewStart.cshtml
similarity index 100%
rename from TheIdServer/src/TIS/Views/_ViewStart.cshtml
rename to TheIdServer.Duende/src/TIS/Views/_ViewStart.cshtml
diff --git a/TheIdServer/src/TIS/appsettings.Development.json b/TheIdServer.Duende/src/TIS/appsettings.Development.json
similarity index 100%
rename from TheIdServer/src/TIS/appsettings.Development.json
rename to TheIdServer.Duende/src/TIS/appsettings.Development.json
diff --git a/TheIdServer/src/TIS/appsettings.json b/TheIdServer.Duende/src/TIS/appsettings.json
similarity index 100%
rename from TheIdServer/src/TIS/appsettings.json
rename to TheIdServer.Duende/src/TIS/appsettings.json
diff --git a/TheIdServer/src/TIS/compilerconfig.json b/TheIdServer.Duende/src/TIS/compilerconfig.json
similarity index 100%
rename from TheIdServer/src/TIS/compilerconfig.json
rename to TheIdServer.Duende/src/TIS/compilerconfig.json
diff --git a/TheIdServer/src/TIS/compilerconfig.json.defaults b/TheIdServer.Duende/src/TIS/compilerconfig.json.defaults
similarity index 100%
rename from TheIdServer/src/TIS/compilerconfig.json.defaults
rename to TheIdServer.Duende/src/TIS/compilerconfig.json.defaults
diff --git a/TheIdServer.Duende/src/TIS/keys/is-signing-key-3A78910D233F279B5952A9045A30E48E.json b/TheIdServer.Duende/src/TIS/keys/is-signing-key-3A78910D233F279B5952A9045A30E48E.json
new file mode 100644
index 0000000..e8198fe
--- /dev/null
+++ b/TheIdServer.Duende/src/TIS/keys/is-signing-key-3A78910D233F279B5952A9045A30E48E.json
@@ -0,0 +1 @@
+{"Version":1,"Id":"3A78910D233F279B5952A9045A30E48E","Created":"2021-09-20T13:06:32.8468047Z","Algorithm":"RS256","IsX509Certificate":false,"Data":"CfDJ8HVwx8LbtylPldQbGMbtRV2I-nd1WFuZ_snXm78s6-OSNawyL7Zo-uzG00MMDEnq_be6CCeUOz8mixukq4gkfEY1e32eMDetFJo5QXJrB_KSuTo932kFBbi_37B27dSrQuWUUu5UkKg-j7tZKOxxgfP99lHAosWptMxOCNbwmzMEBXAOqW5iOPvK9TpLvDjhHGu9rGmpXgPuxbCPTEKI-ceLhaR-BTTJXxURU22YENQHnElw9oLqmydwOCFpCRjOluhUif__8iitr99q-TfNJFhISZqeXYstY0FwtLLC80jQXcAr8TSwT6AeOSv4kDtiHDcknIs1JdNCF5FRpbkFLXN2fZLzZ5fwLGcS1c3IyDn37w7qwMvf7S9oOl4cpYzs9jKwPzXD7kkodhBEEZndgTB-zL2ZwijpzZKaqSG8ibkrRP-GlHTMmRWju2iDTPUl6YgMS5lh_FsguuL0HolgRde_lufJvjrW7y9rhbzPgY6pmcNdYvyYH1BddcfEe1YPp3EQ7lqSdA2romys3rVJKrH95MaoflqBqarhxXLFNh5IWILEpvQQLYfBTno_RvPUKyjYxKevgI5zJPt8qdz5FrCg1AsKX8z9Qh_MBSTLQhiXyS2oT1yLbQP2uOWxdaY6B_YzKlT_6SZRPi81rWQcm3-CEkaZxqAn0DdH00hZ1drU8yqWm1D5STpjMqXk-A6fD71mA0YVstPOW-KOKqcIxUDas5-PkMtdVRaBPrtRE55eCZCYtOKKgpQdC_6xoENjzx8Ay7FpmDePNWtFwzckm3fzlsaZABzZYVFD6UaCmjB9k9zQC3qBJ2RliovDppCUmecUmF3fDSpvU3jwy6Scm_gs1iVlSMK_71CNRVeczE24gpF3A424hWZU3BXHVzx0AfebfZD0-xUP3pWaVNLvK8-hqHAydFfX3F485w74wGD8_3KbMHvZTmXd4OPgy_NMIzvdUO2_VVNpz0UZRCKNGvEcR4oTElAHjMIQ4mLKX2X4AYx2avCF4Aeu83vs1wTIG9n4zW5IDRFK__NDlNWWb00ZE8T36S1BUUarTgm02UgqA50cRXmR9rbhCSDXQ2oYGFhjyZUy-p715j6Wp4w9hVWoAqfy2THF7iyUuCML-3_o8jR4MhhU8e6Xg-V66OLpMACCM-E-M-wILTY-XF_5PWbNz70WiY336iRwCD0KXAxDUyYKlbrP7_h-P9MFzD9A-EGSifejfyqGAgzcHHGSlzy-lbe52-UANIqhE_VM3w8AhJCvT4BKXUMpPucIF1fKhoWKD6GYfy4OkLtK2PqaXklCaCBRF2j8rCzHGve-pfESk2Jqiu_I2Zn3TBGoWBxjRJXmAwwOxwNQpO17rTSddFVF0yo0Z6Kno_JB8nsBtDmSlep-jpKsvcq9DZEzwIZtQA6sYw2WSeaE1tgf5ob5bJYFmC0GGEQQcCMSZlgxL4Q_GCzN_1M69-DYZ4ezS25nnqLBIcIFlPdCHeHNKr_jjWlmZ812CWsy8wZ33zY_5zZJhnC3WCOVQtvdh8SBLZ8RK7Un7UeaGuEcFULmjgoL03xmzjppNn_YLQXWJCnbgSiyRee1xhj9vZR3wdcalj0huxdl5jl3E1DXKnJlehakpNSGGNOYhy43b6u4SJR4WW6OOTj1A4Mq71oPiwRWGkAPBGxOCJbg6eGXmGGvo5kVDoV48Gi6wtBuHz3YuMNiIppm8GnWGGLPrYnxG-2hkwH-KkxFLwwV894WMORz2E0EVWhp2Nug2LbcWYUWtGwliDdzhdaz_1kcmiNGAV-x7QaUos1x0T63ELgVjAi4ARffKLjmulkq3NZVc7zuA7YITENVr-BhmehiUho_4HYVFkYT4KcX6FeMn-8KcbPZyrZJ_QdcRjls4mDNIebemKEmddKGBArHXEby--XPhxzOSzYHMIuBjwDSPMYAQoq_KlFidu0OxJX1o5ryHXIBzHZ6BMnL1gZ8RksI5OYtXOIk52cqp_CBzTSElKEcPwatFrvOIK2X6VCzpo9M5VnYOysSqAF9Xmy1ajWVf8-YfPWTtK1uhl_Oq_vP-U9IHvg_mLs3gkd4mFATTDrg4NTM0qFCZhmPxUilVujxi7ZN_DtSKXAbfg7RTUiQ3FRJAfY9WKW8F-jrsY8FI20XCHD-hSEKRK83RiWmoIGQOmqfnc69Y3KIjHccRey7kEwcB1LehBcdYKANsCTM2R9MwaBmbXd7HQ0bSISL6jSXRCCjWtZk-_ddvRPFQ8Kpg8pUcdmAlH9FU42MWVyJlOkX2oeEESDf6FTt1p0XsgWa0jTk2W3Eg6ch1trFKNa-A94OpNBoiiOENRb7vbZZ2m59HUiftiMUklh6Ta6qIZXIfOQ-DZ8s4CSdf99AUpz7rbV1OVytneOSmjbO_WmGljaiOO7FOY3qkhx1nYsbRuNYWEm4o_M3yCUmTxJxSuCM0MLbJ-Y4TBNNCFqI8428qlI4Q9FYS-ZfFh02","DataProtected":true}
\ No newline at end of file
diff --git a/TheIdServer.Duende/src/TIS/keys/is-signing-key-90393C859E4EE9F17698B6C2B960C840.json b/TheIdServer.Duende/src/TIS/keys/is-signing-key-90393C859E4EE9F17698B6C2B960C840.json
new file mode 100644
index 0000000..bcc3a50
--- /dev/null
+++ b/TheIdServer.Duende/src/TIS/keys/is-signing-key-90393C859E4EE9F17698B6C2B960C840.json
@@ -0,0 +1 @@
+{"Version":1,"Id":"90393C859E4EE9F17698B6C2B960C840","Created":"2021-09-20T13:04:49.4282163Z","Algorithm":"RS256","IsX509Certificate":false,"Data":"CfDJ8NdE5enn8m9CmhkMPYtmtmRCCdQLOk5WANey4YpSWbx6X2NHBu_tdvqcuaUB7wrZhmFzFRaMHsflmrV5wDipbNstTM6hhGWbZPbhpsKQpKEAJ8HiyqgqOJvEtoXavOR3X_NUFUAmv5uwl47i86ZdSFd0RzqBMAmpRtfGFcD01Ot_0SUiDgUBOhI5i7ylafZ02pEJe23xZPRDz6fEvkEZnxKmJHeyNVoPWlEsJe6vdiwhNEQey4R0qORRnWNRJcdxu7ujrLtHdoSdPyU6926-2dAH3hyJ__1PdPqBhhrJOHKrpGyOrBEXdIjm8GF_pEfjhmaSxVZmLZyjhOfdHm96CSRuvRMOqKua434rFYGb01YLrmyRnPQIBSJlDI6UkPm8LieI14R1MaRyfjx7brX4P9E3tMmLdx4DnP0drjSm5gTWMt1mHsiwF9x2wtNGC9ujAxkZ8aoPkpSPjd0m3wrvASBnr4kIhpOGGmDufhoE_s9nilDglIhmWzdWEOnkl91QFJoTwRjLYW16-kQqae6W7VG6ISMV6SOmd8hLTW1Es2DP5nOemnV-f7qqlQiNRYPJ6zx-puzVeBCjuYFGrnxSApLkeILVu5XKMUBeuamIgYxEgZRuwjrciBa4FlugMKtgu5bGIpV-kWQa2AvKEE74Fzb4ayDl9J7uniJ0xHG1BHDN7iLuL7L1wWunUAqZn5XKxgS2wj80B2ksYmr1yyXmwqME5WEHB0ENXGSkkeH3U6iTeWBnhbvTkvao1bP3UKedxedkouJ0QNygOfU2iDFXdbHKPib2N2AFe4bPtIchdWu4crlYynu5zJLWzXPD0t_FkcoJdMu49rry13Z9-HxXS7K0za8vMr7MOt2mLxCgHgjJ2Dd2K5YryqHiqX7zFAbBaP_q2dp1_tOgDATXFiMU0euZPOgBwz2T420WVcueGkgVaVYwhpgLsjO-ctVlE9_-b9XRd4nVOimP2keEaAbD6f0fiPmXMJOBBxLiS_hh26r5_pGe3e_KDpg2tOBElsst5dKlsIPkHYM1o_JwMWPag5ppUnZZbqorBi9Bl_riN6mHBM6Y6pSu64j3kxUlc5XdUXpmTmwynfW87J9a9DqZRUVmmNSWCgcSfRyoZVLNuJ8FllgloBz5r1xzZqyG2GPrANdYdQN4q76OszYPQ-lDvwDv27vQ1wha1_Itfa9sur6nUMqU8-JrAk9J2KtagUDhWF5joSrfotitItH6pJLjsU2V1sTBNge6p8eUmv8vsXONUUbfDRRF0k75XdLzf9NlCZjGtyReOhiTWM5tlh769tJwkOhP1dcMCLVc3aGzW_6YG42a7hInjiyoTdn0f7O8tQBhC9eZXPSw7kfYDhG6k7Sjo0a7mazVjoXxa2G5uxEPurjipcFlC55JmR_FHEJn3pV4pFaGoI1baWZGzYw5FPJ6SnXKhOvCwk8Tut46Ai83VS1Tzh6NOOMkNytKgB2m7i9YbGQiyvjC-bGtWGhp-qntB3TY5UehYGWSdA2QqqF54JUqofv8UWCdd9GqCSzPKnEluY23GrBfcr-fWA7foUi99gnTfDSFfUvUVzq6xxh9JF4U_F3rqOQ0eh1oiKkCTO6EqetRdODT8AM-MUpTeaeI160Xhc3YtVkpoPrdB0oSe0rCqEqwicfXCWi6hCmJVsGIkF4G0GhgUnPP-7MEYFafW58ca8biqE1p_TebLEhoBD3HzbcoGDJdYLES4cZYKFuTmxJv0qfNPyV1yo9Ytc2bKaE3yJynEqLg6mGsisX5eYlM84wGN9FoPmxP06C3jlUKCSQGCkXESjfRyU6vQDa3wsp7fInvlFkjwvqAYwI-c7Un0fF1zvD8xcqrjRn6GK20cGR7fXH0smxw1ixk53FWIKbJZjad4vuqOv2jR-bYpZv1qsaQa4np4UqBmS6pcra717mYyXjx_fYMQuvEXV973oBKNrYiS-qvoAWJZ2vUm8wQ4Qqn7e7VDXs7BdmgA37Q_vUAxD7-BUDckO__u3J6_gY-efFJRaOUdF8Nd0n0Vt_kR0Q5Aep85T2zAhNR8NdTpHPdDhor5EqF8MeJy4_Me9JSATrjgJOUFnoPTF3-jt2IipLrf-r3RnyUR_1BlwWfiaN08tIxvetQjAFdUwmcn4Ga8pAWTjjXTiRxIzmGy_Ej4ORQg4_RoqZFDQX2HA3qFUMx2fGxMSnws6MeoFDHsXzFHIGEax_5zl9ud-55rJNFSyQ_Qj7H5fuRDxeA5RYAyre3NNxQfowx2fuMeZLUOOt73NCHVgFeBZR3GhAzFYaC3KVqAQQ9qg5JUcLOG0gtpxMB4w5aDn1aFANQhCeTrNZEWXD81z-sIp0sPCy3Sg1ji2M046FozT80iXJr-gF0GO3wePoumx-q2n0o7RMAz8NlxFpPglBsuzPz0V7s6WgjnY9BNOcFFk6D0S0blkfwV5-QkqYd4Tw1Cmg-_Om3KpMk3ZBnnLexS5gD7VMY","DataProtected":true}
\ No newline at end of file
diff --git a/TheIdServer/src/TIS/libman.json b/TheIdServer.Duende/src/TIS/libman.json
similarity index 100%
rename from TheIdServer/src/TIS/libman.json
rename to TheIdServer.Duende/src/TIS/libman.json
diff --git a/TheIdServer.Duende/src/TIS/updateUI.ps1 b/TheIdServer.Duende/src/TIS/updateUI.ps1
new file mode 100644
index 0000000..619bc78
--- /dev/null
+++ b/TheIdServer.Duende/src/TIS/updateUI.ps1
@@ -0,0 +1,172 @@
+iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/DuendeSoftware/IdentityServer.Quickstart.UI/main/getmain.ps1'))
+# SIG # Begin signature block
+# MIIfnQYJKoZIhvcNAQcCoIIfjjCCH4oCAQExDzANBglghkgBZQMEAgEFADB5Bgor
+# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
+# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAmjChzA8Tk4hB4
+# nAolI/h9hR7E40n+o1koeC4dCBgwCqCCDgcwggPFMIICraADAgECAhACrFwmagtA
+# m48LefKuRiV3MA0GCSqGSIb3DQEBBQUAMGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
+# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKzApBgNV
+# BAMTIkRpZ2lDZXJ0IEhpZ2ggQXNzdXJhbmNlIEVWIFJvb3QgQ0EwHhcNMDYxMTEw
+# MDAwMDAwWhcNMzExMTEwMDAwMDAwWjBsMQswCQYDVQQGEwJVUzEVMBMGA1UEChMM
+# RGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSswKQYDVQQD
+# EyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMIIBIjANBgkqhkiG
+# 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxszlc+b71LvlLS0ypt/lgT/JzSVJtnEqw9WU
+# NGeiChywX2mmQLHEt7KP0JikqUFZOtPclNY823Q4pErMTSWC90qlUxI47vNJbXGR
+# fmO2q6Zfw6SE+E9iUb74xezbOJLjBuUIkQzEKEFV+8taiRV+ceg1v01yCT2+OjhQ
+# W3cxG42zxyRFmqesbQAUWgS3uhPrUQqYQUEiTmVhh4FBUKZ5XIneGUpX1S7mXRxT
+# LH6YzRoGFqRoc9A0BBNcoXHTWnxV215k4TeHMFYE5RG0KYAS8Xk5iKICEXwnZreI
+# t3jyygqoOKsKZMK/Zl2VhMGhJR6HXRpQCyASzEG7bgtROLhLywIDAQABo2MwYTAO
+# BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUsT7DaQP4
+# v0cB1JgmGggC72NkK8MwHwYDVR0jBBgwFoAUsT7DaQP4v0cB1JgmGggC72NkK8Mw
+# DQYJKoZIhvcNAQEFBQADggEBABwaBpfc15yfPIhmBghXIdshR/gqZ6q/GDJ2QBBX
+# wYrzetkRZY41+p78RbWe2UwxS7iR6EMsjrN4ztvjU3lx1uUhlAHaVYeaJGT2imbM
+# 3pw3zag0sWmbI8ieeCIrcEPjVUcxYRnvWMWFL04w9qAxFiPI5+JlFjPLvxoboD34
+# yl6LMYtgCIktDAZcUrfE+QqY0RVfnxK+fDZjOL1EpH/kJisKxJdpDemM4sAQV7jI
+# dhKRVfJIadi8KgJbD0TUIDHb9LpwJl2QYJ68SxcJL7TLHkNoyQcnwdJc9+ohuWgS
+# nDycv578gFybY83sR6olJ2egN/MAgn1U16n46S4To3foH0owggSRMIIDeaADAgEC
+# AhAHsEGNpR4UjDMbvN63E4MjMA0GCSqGSIb3DQEBCwUAMGwxCzAJBgNVBAYTAlVT
+# MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
+# b20xKzApBgNVBAMTIkRpZ2lDZXJ0IEhpZ2ggQXNzdXJhbmNlIEVWIFJvb3QgQ0Ew
+# HhcNMTgwNDI3MTI0MTU5WhcNMjgwNDI3MTI0MTU5WjBaMQswCQYDVQQGEwJVUzEY
+# MBYGA1UEChMPLk5FVCBGb3VuZGF0aW9uMTEwLwYDVQQDEyguTkVUIEZvdW5kYXRp
+# b24gUHJvamVjdHMgQ29kZSBTaWduaW5nIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC
+# AQ8AMIIBCgKCAQEAwQqv4aI0CI20XeYqTTZmyoxsSQgcCBGQnXnufbuDLhAB6GoT
+# NB7HuEhNSS8ftV+6yq8GztBzYAJ0lALdBjWypMfL451/84AO5ZiZB3V7MB2uxgWo
+# cV1ekDduU9bm1Q48jmR4SVkLItC+oQO/FIA2SBudVZUvYKeCJS5Ri9ibV7La4oo7
+# BJChFiP8uR+v3OU33dgm5BBhWmth4oTyq22zCfP3NO6gBWEIPFR5S+KcefUTYmn2
+# o7IvhvxzJsMCrNH1bxhwOyMl+DQcdWiVPuJBKDOO/hAKIxBG4i6ryQYBaKdhDgaA
+# NSCik0UgZasz8Qgl8n0A73+dISPumD8L/4mdywIDAQABo4IBPzCCATswHQYDVR0O
+# BBYEFMtck66Im/5Db1ZQUgJtePys4bFaMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY
+# JhoIAu9jZCvDMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDAzAS
+# BgNVHRMBAf8ECDAGAQH/AgEAMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY
+# aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEsGA1UdHwREMEIwQKA+oDyGOmh0dHA6
+# Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEhpZ2hBc3N1cmFuY2VFVlJvb3RD
+# QS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v
+# d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwDQYJKoZIhvcNAQELBQADggEBALNGxKTz6gq6
+# clMF01GjC3RmJ/ZAoK1V7rwkqOkY3JDl++v1F4KrFWEzS8MbZsI/p4W31Eketazo
+# Nxy23RT0zDsvJrwEC3R+/MRdkB7aTecsYmMeMHgtUrl3xEO3FubnQ0kKEU/HBCTd
+# hR14GsQEccQQE6grFVlglrew+FzehWUu3SUQEp9t+iWpX/KfviDWx0H1azilMX15
+# lzJUxK7kCzmflrk5jCOCjKqhOdGJoQqstmwP+07qXO18bcCzEC908P+TYkh0z9gV
+# rlj7tyW9K9zPVPJZsLRaBp/QjMcH65o9Y1hD1uWtFQYmbEYkT1K9tuXHtQYx1Rpf
+# /dC8Nbl4iukwggWlMIIEjaADAgECAhAL5Ofkz0TFYBmonpg7pehYMA0GCSqGSIb3
+# DQEBCwUAMFoxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw8uTkVUIEZvdW5kYXRpb24x
+# MTAvBgNVBAMTKC5ORVQgRm91bmRhdGlvbiBQcm9qZWN0cyBDb2RlIFNpZ25pbmcg
+# Q0EwHhcNMTgwNjExMDAwMDAwWhcNMjEwNjE1MTIwMDAwWjCBoDEUMBIGA1UEBRML
+# NjAzIDM4OSAwNjgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
+# DgYDVQQHEwdSZWRtb25kMSkwJwYDVQQKEyBJZGVudGl0eVNlcnZlciAoLk5FVCBG
+# b3VuZGF0aW9uKTEpMCcGA1UEAxMgSWRlbnRpdHlTZXJ2ZXIgKC5ORVQgRm91bmRh
+# dGlvbikwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCwEhFpSIYi3hrr
+# v/X9BtZkzufk7puhmTCVmQAPNm2R1eZZyMPhfxm5Sh/w/42CzlCG9LFgooha69z3
+# uoMMOKJEEQKZ6ByIV+r81o4lrHtSFbe4VlXavjQVFaVVjPSG6vWGykfHVCAeVpjx
+# fVk/HH6tEX506lBiHgOrQGogoQrwdVnObc3c6RiVSIuvFeCoHvk2GgiqyzFER7iO
+# R1055npVSAAAdxBvPA6KREcLb/qHukYCJZX4mY/SajBXwxupSnhRDbYhb+qHpFIL
+# x7s/azxg7tVRpVh49oJimHA2uZ/jzh/KgUsUe9MFzT7KPduBK/pfX/fXED9Pt1NN
+# 48VfPSuzAgMBAAGjggIeMIICGjAfBgNVHSMEGDAWgBTLXJOuiJv+Q29WUFICbXj8
+# rOGxWjAdBgNVHQ4EFgQU3CQnBPLvFkovKU/is0/LgQF/49swNAYDVR0RBC0wK6Ap
+# BggrBgEFBQcIA6AdMBsMGVVTLVdBU0hJTkdUT04tNjAzIDM4OSAwNjgwDgYDVR0P
+# AQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMIGZBgNVHR8EgZEwgY4wRaBD
+# oEGGP2h0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9ORVRGb3VuZGF0aW9uUHJvamVj
+# dHNDb2RlU2lnbmluZ0NBLmNybDBFoEOgQYY/aHR0cDovL2NybDQuZGlnaWNlcnQu
+# Y29tL05FVEZvdW5kYXRpb25Qcm9qZWN0c0NvZGVTaWduaW5nQ0EuY3JsMEwGA1Ud
+# IARFMEMwNwYJYIZIAYb9bAMBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRp
+# Z2ljZXJ0LmNvbS9DUFMwCAYGZ4EMAQQBMIGEBggrBgEFBQcBAQR4MHYwJAYIKwYB
+# BQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBOBggrBgEFBQcwAoZCaHR0
+# cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL05FVEZvdW5kYXRpb25Qcm9qZWN0c0Nv
+# ZGVTaWduaW5nQ0EuY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggEB
+# AH0FqXX4p5RlC27jX6tcTEf2HT4mAiqosnYU/napWgQE2U9m73IZO+2MuiQWkUQi
+# 2PLjbQQcOwfMwkt0SDaSAlfC1zhjZkZb2NcpJRg0cUAjcDzqh6hTzXRVJPD/UrW2
+# a5qBhYnSQDSWbYnVwfAQFFvnQcR5i/xnoOxq7+3LIvHoJafpsxcAFS57Vdsuw91u
+# keB6uasOfvdd06Mpl9BLWZHpyEdnPIKMv6ALibTdw9lNzCQ+EmdT5Fwky8wHE8BH
+# hhAdjSuGiyd+AzR3IuL96Q41h34c7pL827atOHwkkjUx+QTVkXbYoal6wwBKhi6I
+# QJEhT0s/yyFFM7BrLhQpRSsxghDsMIIQ6AIBATBuMFoxCzAJBgNVBAYTAlVTMRgw
+# FgYDVQQKEw8uTkVUIEZvdW5kYXRpb24xMTAvBgNVBAMTKC5ORVQgRm91bmRhdGlv
+# biBQcm9qZWN0cyBDb2RlIFNpZ25pbmcgQ0ECEAvk5+TPRMVgGaiemDul6FgwDQYJ
+# YIZIAWUDBAIBBQCggYQwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkqhkiG
+# 9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIB
+# FTAvBgkqhkiG9w0BCQQxIgQg3M8y2Uovs3GZZGILEzOaZ5eGje/9PkpzoByKnbYv
+# zTYwDQYJKoZIhvcNAQEBBQAEggEAnU9xhSBP/Rv/Cgvmzp0LcG8pjxPlbjvPef7V
+# xvkeQzTvMHYg0LiveMH1y94PyfBdxqUAGRuCblBOmlECw24w8Hdv8dY2LQMhqJu8
+# Qt08UVYtiJQBHwclFmnK8ER8g/Wc5LPsuvzoYzTvvr/FS2wSCaMV7p2WHIwjJxXR
+# sLaccoU2fuPbmE1WVPsjy3ttzkmfyhbl3Mc2QXiLNTQJ+gSNBg2s2eSzf7eDcM2Z
+# kysuAEdu4kKhSsHOhkkpBHffplg9pYrehbUq8QJ+T/9e0vHPbbP5xIPaRqvBEMWy
+# ydh0LWvK6GnJ+/yPHjddtrjpY4ncTlK5ii+wyzyZc9k4rryOKaGCDsgwgg7EBgor
+# BgEEAYI3AwMBMYIOtDCCDrAGCSqGSIb3DQEHAqCCDqEwgg6dAgEDMQ8wDQYJYIZI
+# AWUDBAIBBQAwdwYLKoZIhvcNAQkQAQSgaARmMGQCAQEGCWCGSAGG/WwHATAxMA0G
+# CWCGSAFlAwQCAQUABCDIQfwOJSjewgHOlovqbOQetKuLbiiSjibgzLTQTnzqLAIQ
+# Lslu2kB0PjG9aO0ua2h6ihgPMjAyMDA3MDEwNzMyNTBaoIILuzCCBoIwggVqoAMC
+# AQICEATNP4VornbGG7D+cWDMp20wDQYJKoZIhvcNAQELBQAwcjELMAkGA1UEBhMC
+# VVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0
+# LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIFRpbWVzdGFt
+# cGluZyBDQTAeFw0xOTEwMDEwMDAwMDBaFw0zMDEwMTcwMDAwMDBaMEwxCzAJBgNV
+# BAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEkMCIGA1UEAxMbVElNRVNU
+# QU1QLVNIQTI1Ni0yMDE5LTEwLTE1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+# CgKCAQEA6WQ1nPqpmGVkG+QX3LgpNsxnCViFTTDgyf/lOzwRKFCvBzHiXQkYwvaJ
+# jGkIBCPgdy2dFeW46KFqjv/UrtJ6Fu/4QbUdOXXBzy+nrEV+lG2sAwGZPGI+fnr9
+# RZcxtPq32UI+p1Wb31pPWAKoMmkiE76Lgi3GmKtrm7TJ8mURDHQNsvAIlnTE6LJI
+# oqEUpfj64YlwRDuN7/uk9MO5vRQs6wwoJyWAqxBLFhJgC2kijE7NxtWyZVkh4Hws
+# Eo1wDo+KyuDT17M5d1DQQiwues6cZ3o4d1RA/0+VBCDU68jOhxQI/h2A3dDnK3jq
+# vx9wxu5CFlM2RZtTGUlinXoCm5UUowIDAQABo4IDODCCAzQwDgYDVR0PAQH/BAQD
+# AgeAMAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwggG/BgNV
+# HSAEggG2MIIBsjCCAaEGCWCGSAGG/WwHATCCAZIwKAYIKwYBBQUHAgEWHGh0dHBz
+# Oi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwggFkBggrBgEFBQcCAjCCAVYeggFSAEEA
+# bgB5ACAAdQBzAGUAIABvAGYAIAB0AGgAaQBzACAAQwBlAHIAdABpAGYAaQBjAGEA
+# dABlACAAYwBvAG4AcwB0AGkAdAB1AHQAZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMA
+# ZQAgAG8AZgAgAHQAaABlACAARABpAGcAaQBDAGUAcgB0ACAAQwBQAC8AQwBQAFMA
+# IABhAG4AZAAgAHQAaABlACAAUgBlAGwAeQBpAG4AZwAgAFAAYQByAHQAeQAgAEEA
+# ZwByAGUAZQBtAGUAbgB0ACAAdwBoAGkAYwBoACAAbABpAG0AaQB0ACAAbABpAGEA
+# YgBpAGwAaQB0AHkAIABhAG4AZAAgAGEAcgBlACAAaQBuAGMAbwByAHAAbwByAGEA
+# dABlAGQAIABoAGUAcgBlAGkAbgAgAGIAeQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4w
+# CwYJYIZIAYb9bAMVMB8GA1UdIwQYMBaAFPS24SAd/imu0uRhpbKiJbLIFzVuMB0G
+# A1UdDgQWBBRWUw/BxgenTdfYbldygFBM5OyewTBxBgNVHR8EajBoMDKgMKAuhixo
+# dHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1hc3N1cmVkLXRzLmNybDAyoDCg
+# LoYsaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC10cy5jcmww
+# gYUGCCsGAQUFBwEBBHkwdzAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNl
+# cnQuY29tME8GCCsGAQUFBzAChkNodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20v
+# RGlnaUNlcnRTSEEyQXNzdXJlZElEVGltZXN0YW1waW5nQ0EuY3J0MA0GCSqGSIb3
+# DQEBCwUAA4IBAQAug6FEBUoE47kyUvrZgfAau/gJjSO5PdiSoeZGHEovbno8Y243
+# F6Mav1gjskOclINOOQmwLOjH4eLM7ct5a87eIwFH7ZVUgeCAexKxrwKGqTpzav74
+# n8GN0SGM5CmCw4oLYAACnR9HxJ+0CmhTf1oQpvgi5vhTkjFf2IKDLW0TQq6DwRBO
+# pCT0R5zeDyJyd1x/T+k5mCtXkkTX726T2UPHBDNjUTdWnkcEEcOjWFQh2OKOVtdJ
+# P1f8Cp8jXnv0lI3dnRq733oqptJFplUMj/ZMivKWz4lG3DGykZCjXzMwYFX1/Gsw
+# rKHt5EdOM55naii1TcLtW5eC+MupCGxTCbT3MIIFMTCCBBmgAwIBAgIQCqEl1tYy
+# G35B5AXaNpfCFTANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UE
+# ChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD
+# VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMTYwMTA3MTIwMDAw
+# WhcNMzEwMTA3MTIwMDAwWjByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNl
+# cnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdp
+# Q2VydCBTSEEyIEFzc3VyZWQgSUQgVGltZXN0YW1waW5nIENBMIIBIjANBgkqhkiG
+# 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvdAy7kvNj3/dqbqCmcU5VChXtiNKxA4HRTNR
+# EH3Q+X1NaH7ntqD0jbOI5Je/YyGQmL8TvFfTw+F+CNZqFAA49y4eO+7MpvYyWf5f
+# ZT/gm+vjRkcGGlV+Cyd+wKL1oODeIj8O/36V+/OjuiI+GKwR5PCZA207hXwJ0+5d
+# yJoLVOOoCXFr4M8iEA91z3FyTgqt30A6XLdR4aF5FMZNJCMwXbzsPGBqrC8HzP3w
+# 6kfZiFBe/WZuVmEnKYmEUeaC50ZQ/ZQqLKfkdT66mA+Ef58xFNat1fJky3seBdCE
+# GXIX8RcG7z3N1k3vBkL9olMqT4UdxB08r8/arBD13ays6Vb/kwIDAQABo4IBzjCC
+# AcowHQYDVR0OBBYEFPS24SAd/imu0uRhpbKiJbLIFzVuMB8GA1UdIwQYMBaAFEXr
+# oq/0ksuCMS1Ri6enIZ3zbcgPMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/
+# BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHkGCCsGAQUFBwEBBG0wazAkBggr
+# BgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUFBzAChjdo
+# dHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290
+# Q0EuY3J0MIGBBgNVHR8EejB4MDqgOKA2hjRodHRwOi8vY3JsNC5kaWdpY2VydC5j
+# b20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMDqgOKA2hjRodHRwOi8vY3Js
+# My5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMFAGA1Ud
+# IARJMEcwOAYKYIZIAYb9bAACBDAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5k
+# aWdpY2VydC5jb20vQ1BTMAsGCWCGSAGG/WwHATANBgkqhkiG9w0BAQsFAAOCAQEA
+# cZUS6VGHVmnN793afKpjerN4zwY3QITvS4S/ys8DAv3Fp8MOIEIsr3fzKx8MIVoq
+# twU0HWqumfgnoma/Capg33akOpMP+LLR2HwZYuhegiUexLoceywh4tZbLBQ1QwRo
+# stt1AuByx5jWPGTlH0gQGF+JOGFNYkYkh2OMkVIsrymJ5Xgf1gsUpYDXEkdws3XV
+# k4WTfraSZ/tTYYmo9WuWwPRYaQ18yAGxuSh1t5ljhSKMYcp5lH5Z/IwP42+1ASa2
+# bKXuh1Eh5Fhgm7oMLSttosR+u8QlK0cCCHxJrhO24XxCQijGGFbPQTS2Zl22dHv1
+# VjMiLyI2skuiSpXY9aaOUjGCAk0wggJJAgEBMIGGMHIxCzAJBgNVBAYTAlVTMRUw
+# EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x
+# MTAvBgNVBAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBUaW1lc3RhbXBpbmcg
+# Q0ECEATNP4VornbGG7D+cWDMp20wDQYJYIZIAWUDBAIBBQCggZgwGgYJKoZIhvcN
+# AQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yMDA3MDEwNzMyNTBa
+# MCsGCyqGSIb3DQEJEAIMMRwwGjAYMBYEFAMlvVBe2pYwLcIvT6AeTCi+KDTFMC8G
+# CSqGSIb3DQEJBDEiBCBZP0neSFIOf2DML1gBlDUAamr0e9Eq+Rq4UdHiDVVe4zAN
+# BgkqhkiG9w0BAQEFAASCAQAxWJwxav4qQZCfWAiXHhmKYZ8QwsfDeyq3kuGvhN1K
+# muulWU/5dlfTaQQ8wF5BKdqHm31YJfAwRqQmJyuuh66Fx7pmRTvD5zQaH2NKaW73
+# hKZawvMaQlrMj4vT2WuepoQbQEyjhpunPXgF4vf8Gmr787CbY6D/M2v9in1MFjRV
+# HJk2+rgQmH+hfg7vgLbzHHGFMQ25K6F0/61r9MXq7NzT3iksdGYh7wM7q0KWxy5Q
+# blWF6iyA39GqTC6ZghHWIL+lWv7JKtzjwF06U+PaqCvZMhzlFNxYFYiKJ7uqcSf5
+# zroMfSjDExq6ZFVH9qiiXEA4RXrN5cDwXjwKnOdYD5dA
+# SIG # End signature block
diff --git a/TheIdServer/src/TIS/wwwroot/css/site.css b/TheIdServer.Duende/src/TIS/wwwroot/css/site.css
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/css/site.css
rename to TheIdServer.Duende/src/TIS/wwwroot/css/site.css
diff --git a/TheIdServer/src/TIS/wwwroot/css/site.less b/TheIdServer.Duende/src/TIS/wwwroot/css/site.less
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/css/site.less
rename to TheIdServer.Duende/src/TIS/wwwroot/css/site.less
diff --git a/TheIdServer/src/TIS/wwwroot/css/site.min.css b/TheIdServer.Duende/src/TIS/wwwroot/css/site.min.css
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/css/site.min.css
rename to TheIdServer.Duende/src/TIS/wwwroot/css/site.min.css
diff --git a/TheIdServer/src/TIS/wwwroot/favicon.ico b/TheIdServer.Duende/src/TIS/wwwroot/favicon.ico
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/favicon.ico
rename to TheIdServer.Duende/src/TIS/wwwroot/favicon.ico
diff --git a/TheIdServer/src/TIS/wwwroot/icon.jpg b/TheIdServer.Duende/src/TIS/wwwroot/icon.jpg
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/icon.jpg
rename to TheIdServer.Duende/src/TIS/wwwroot/icon.jpg
diff --git a/TheIdServer/src/TIS/wwwroot/icon.png b/TheIdServer.Duende/src/TIS/wwwroot/icon.png
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/icon.png
rename to TheIdServer.Duende/src/TIS/wwwroot/icon.png
diff --git a/TheIdServer/src/TIS/wwwroot/icons8-app-symbol-96.png b/TheIdServer.Duende/src/TIS/wwwroot/icons8-app-symbol-96.png
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/icons8-app-symbol-96.png
rename to TheIdServer.Duende/src/TIS/wwwroot/icons8-app-symbol-96.png
diff --git a/TheIdServer/src/TIS/wwwroot/js/signin-redirect.js b/TheIdServer.Duende/src/TIS/wwwroot/js/signin-redirect.js
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/js/signin-redirect.js
rename to TheIdServer.Duende/src/TIS/wwwroot/js/signin-redirect.js
diff --git a/TheIdServer/src/TIS/wwwroot/js/signout-redirect.js b/TheIdServer.Duende/src/TIS/wwwroot/js/signout-redirect.js
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/js/signout-redirect.js
rename to TheIdServer.Duende/src/TIS/wwwroot/js/signout-redirect.js
diff --git a/TheIdServer/src/TIS/wwwroot/lib/bootstrap/css/bootstrap.css b/TheIdServer.Duende/src/TIS/wwwroot/lib/bootstrap/css/bootstrap.css
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/lib/bootstrap/css/bootstrap.css
rename to TheIdServer.Duende/src/TIS/wwwroot/lib/bootstrap/css/bootstrap.css
diff --git a/TheIdServer/src/TIS/wwwroot/lib/bootstrap/css/bootstrap.css.map b/TheIdServer.Duende/src/TIS/wwwroot/lib/bootstrap/css/bootstrap.css.map
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/lib/bootstrap/css/bootstrap.css.map
rename to TheIdServer.Duende/src/TIS/wwwroot/lib/bootstrap/css/bootstrap.css.map
diff --git a/TheIdServer/src/TIS/wwwroot/lib/bootstrap/css/bootstrap.min.css b/TheIdServer.Duende/src/TIS/wwwroot/lib/bootstrap/css/bootstrap.min.css
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/lib/bootstrap/css/bootstrap.min.css
rename to TheIdServer.Duende/src/TIS/wwwroot/lib/bootstrap/css/bootstrap.min.css
diff --git a/TheIdServer/src/TIS/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.eot b/TheIdServer.Duende/src/TIS/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.eot
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.eot
rename to TheIdServer.Duende/src/TIS/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.eot
diff --git a/TheIdServer/src/TIS/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.svg b/TheIdServer.Duende/src/TIS/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.svg
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.svg
rename to TheIdServer.Duende/src/TIS/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.svg
diff --git a/TheIdServer/src/TIS/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf b/TheIdServer.Duende/src/TIS/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf
rename to TheIdServer.Duende/src/TIS/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf
diff --git a/TheIdServer/src/TIS/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff b/TheIdServer.Duende/src/TIS/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff
rename to TheIdServer.Duende/src/TIS/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff
diff --git a/TheIdServer/src/TIS/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2 b/TheIdServer.Duende/src/TIS/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2
rename to TheIdServer.Duende/src/TIS/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2
diff --git a/TheIdServer/src/TIS/wwwroot/lib/bootstrap/js/bootstrap.js b/TheIdServer.Duende/src/TIS/wwwroot/lib/bootstrap/js/bootstrap.js
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/lib/bootstrap/js/bootstrap.js
rename to TheIdServer.Duende/src/TIS/wwwroot/lib/bootstrap/js/bootstrap.js
diff --git a/TheIdServer/src/TIS/wwwroot/lib/bootstrap/js/bootstrap.min.js b/TheIdServer.Duende/src/TIS/wwwroot/lib/bootstrap/js/bootstrap.min.js
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/lib/bootstrap/js/bootstrap.min.js
rename to TheIdServer.Duende/src/TIS/wwwroot/lib/bootstrap/js/bootstrap.min.js
diff --git a/TheIdServer/src/TIS/wwwroot/lib/jquery-validation/build/release.js b/TheIdServer.Duende/src/TIS/wwwroot/lib/jquery-validation/build/release.js
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/lib/jquery-validation/build/release.js
rename to TheIdServer.Duende/src/TIS/wwwroot/lib/jquery-validation/build/release.js
diff --git a/TheIdServer/src/TIS/wwwroot/lib/jquery/jquery.js b/TheIdServer.Duende/src/TIS/wwwroot/lib/jquery/jquery.js
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/lib/jquery/jquery.js
rename to TheIdServer.Duende/src/TIS/wwwroot/lib/jquery/jquery.js
diff --git a/TheIdServer/src/TIS/wwwroot/lib/jquery/jquery.min.js b/TheIdServer.Duende/src/TIS/wwwroot/lib/jquery/jquery.min.js
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/lib/jquery/jquery.min.js
rename to TheIdServer.Duende/src/TIS/wwwroot/lib/jquery/jquery.min.js
diff --git a/TheIdServer/src/TIS/wwwroot/lib/jquery/jquery.min.map b/TheIdServer.Duende/src/TIS/wwwroot/lib/jquery/jquery.min.map
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/lib/jquery/jquery.min.map
rename to TheIdServer.Duende/src/TIS/wwwroot/lib/jquery/jquery.min.map
diff --git a/TheIdServer/src/TIS/wwwroot/lib/qrcode.js b/TheIdServer.Duende/src/TIS/wwwroot/lib/qrcode.js
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/lib/qrcode.js
rename to TheIdServer.Duende/src/TIS/wwwroot/lib/qrcode.js
diff --git a/TheIdServer/src/TIS/wwwroot/logo.png b/TheIdServer.Duende/src/TIS/wwwroot/logo.png
similarity index 100%
rename from TheIdServer/src/TIS/wwwroot/logo.png
rename to TheIdServer.Duende/src/TIS/wwwroot/logo.png
diff --git a/TheIdServer.Duende/src/TIS/wwwroot/welcome-fragment.fr.html b/TheIdServer.Duende/src/TIS/wwwroot/welcome-fragment.fr.html
new file mode 100644
index 0000000..50323c1
--- /dev/null
+++ b/TheIdServer.Duende/src/TIS/wwwroot/welcome-fragment.fr.html
@@ -0,0 +1,9 @@
+
+ Cette application gère votre TheIdServer .
+ Un serveur OpenID/Connect , OAuth2
+ et WS-Federation
+ basé sur Duende IdentityServer .
+
+
+ Visitez le site github pour la documentation, le code source et le suivi des problèmes.
+
\ No newline at end of file
diff --git a/TheIdServer.Duende/src/TIS/wwwroot/welcome-fragment.html b/TheIdServer.Duende/src/TIS/wwwroot/welcome-fragment.html
new file mode 100644
index 0000000..3ad811f
--- /dev/null
+++ b/TheIdServer.Duende/src/TIS/wwwroot/welcome-fragment.html
@@ -0,0 +1,9 @@
+
+ This application manage your TheIdServer .
+ An OpenID/Connect , OAuth2
+ and WS-Federation
+ server based on Duende IdentityServer .
+
+
+ Visit the github site for doc, source code and issue tracking.
+
\ No newline at end of file
diff --git a/TheIdServer/test/Microsoft.AspNetCore.Components.Testing/ContainerComponent.cs b/TheIdServer.Duende/test/Microsoft.AspNetCore.Components.Testing/ContainerComponent.cs
similarity index 100%
rename from TheIdServer/test/Microsoft.AspNetCore.Components.Testing/ContainerComponent.cs
rename to TheIdServer.Duende/test/Microsoft.AspNetCore.Components.Testing/ContainerComponent.cs
diff --git a/TheIdServer/test/Microsoft.AspNetCore.Components.Testing/EventDispatchExtensions.cs b/TheIdServer.Duende/test/Microsoft.AspNetCore.Components.Testing/EventDispatchExtensions.cs
similarity index 100%
rename from TheIdServer/test/Microsoft.AspNetCore.Components.Testing/EventDispatchExtensions.cs
rename to TheIdServer.Duende/test/Microsoft.AspNetCore.Components.Testing/EventDispatchExtensions.cs
diff --git a/TheIdServer/test/Microsoft.AspNetCore.Components.Testing/Htmlizer.cs b/TheIdServer.Duende/test/Microsoft.AspNetCore.Components.Testing/Htmlizer.cs
similarity index 100%
rename from TheIdServer/test/Microsoft.AspNetCore.Components.Testing/Htmlizer.cs
rename to TheIdServer.Duende/test/Microsoft.AspNetCore.Components.Testing/Htmlizer.cs
diff --git a/TheIdServer/test/Microsoft.AspNetCore.Components.Testing/Microsoft.AspNetCore.Components.Testing.csproj b/TheIdServer.Duende/test/Microsoft.AspNetCore.Components.Testing/Microsoft.AspNetCore.Components.Testing.csproj
similarity index 100%
rename from TheIdServer/test/Microsoft.AspNetCore.Components.Testing/Microsoft.AspNetCore.Components.Testing.csproj
rename to TheIdServer.Duende/test/Microsoft.AspNetCore.Components.Testing/Microsoft.AspNetCore.Components.Testing.csproj
diff --git a/TheIdServer/test/Microsoft.AspNetCore.Components.Testing/MockHttpExtensions.cs b/TheIdServer.Duende/test/Microsoft.AspNetCore.Components.Testing/MockHttpExtensions.cs
similarity index 100%
rename from TheIdServer/test/Microsoft.AspNetCore.Components.Testing/MockHttpExtensions.cs
rename to TheIdServer.Duende/test/Microsoft.AspNetCore.Components.Testing/MockHttpExtensions.cs
diff --git a/TheIdServer/test/Microsoft.AspNetCore.Components.Testing/RenderedComponent.cs b/TheIdServer.Duende/test/Microsoft.AspNetCore.Components.Testing/RenderedComponent.cs
similarity index 100%
rename from TheIdServer/test/Microsoft.AspNetCore.Components.Testing/RenderedComponent.cs
rename to TheIdServer.Duende/test/Microsoft.AspNetCore.Components.Testing/RenderedComponent.cs
diff --git a/TheIdServer/test/Microsoft.AspNetCore.Components.Testing/TestHost.cs b/TheIdServer.Duende/test/Microsoft.AspNetCore.Components.Testing/TestHost.cs
similarity index 100%
rename from TheIdServer/test/Microsoft.AspNetCore.Components.Testing/TestHost.cs
rename to TheIdServer.Duende/test/Microsoft.AspNetCore.Components.Testing/TestHost.cs
diff --git a/TheIdServer/test/Microsoft.AspNetCore.Components.Testing/TestHtmlDocument.cs b/TheIdServer.Duende/test/Microsoft.AspNetCore.Components.Testing/TestHtmlDocument.cs
similarity index 100%
rename from TheIdServer/test/Microsoft.AspNetCore.Components.Testing/TestHtmlDocument.cs
rename to TheIdServer.Duende/test/Microsoft.AspNetCore.Components.Testing/TestHtmlDocument.cs
diff --git a/TheIdServer/test/Microsoft.AspNetCore.Components.Testing/TestNavigationManager.cs b/TheIdServer.Duende/test/Microsoft.AspNetCore.Components.Testing/TestNavigationManager.cs
similarity index 100%
rename from TheIdServer/test/Microsoft.AspNetCore.Components.Testing/TestNavigationManager.cs
rename to TheIdServer.Duende/test/Microsoft.AspNetCore.Components.Testing/TestNavigationManager.cs
diff --git a/TheIdServer/test/Microsoft.AspNetCore.Components.Testing/TestRenderer.cs b/TheIdServer.Duende/test/Microsoft.AspNetCore.Components.Testing/TestRenderer.cs
similarity index 100%
rename from TheIdServer/test/Microsoft.AspNetCore.Components.Testing/TestRenderer.cs
rename to TheIdServer.Duende/test/Microsoft.AspNetCore.Components.Testing/TestRenderer.cs
diff --git a/TheIdServer/test/TIS.IntegrationTest/ApiFixture.cs b/TheIdServer.Duende/test/TIS.IntegrationTest/ApiFixture.cs
similarity index 100%
rename from TheIdServer/test/TIS.IntegrationTest/ApiFixture.cs
rename to TheIdServer.Duende/test/TIS.IntegrationTest/ApiFixture.cs
diff --git a/TheIdServer/test/TIS.IntegrationTest/BlazorApp/Shared/MainLayoutTest.cs b/TheIdServer.Duende/test/TIS.IntegrationTest/BlazorApp/Shared/MainLayoutTest.cs
similarity index 100%
rename from TheIdServer/test/TIS.IntegrationTest/BlazorApp/Shared/MainLayoutTest.cs
rename to TheIdServer.Duende/test/TIS.IntegrationTest/BlazorApp/Shared/MainLayoutTest.cs
diff --git a/TheIdServer.Duende/test/TIS.IntegrationTest/Controlers/RegisterControllerTest.cs b/TheIdServer.Duende/test/TIS.IntegrationTest/Controlers/RegisterControllerTest.cs
new file mode 100644
index 0000000..0662321
--- /dev/null
+++ b/TheIdServer.Duende/test/TIS.IntegrationTest/Controlers/RegisterControllerTest.cs
@@ -0,0 +1,1033 @@
+// Copyright (c) 2021 @Olivier Lefebvre. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+using Aguacongas.IdentityServer.Admin.Models;
+using Duende.IdentityServer.Models;
+using Microsoft.Extensions.DependencyInjection;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Security.Claims;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Aguacongas.TheIdServer.IntegrationTest.Controlers
+{
+ public class RegisterControllerTest
+ {
+ [Fact]
+ public async Task CreateAsync_should_register_a_new_client()
+ {
+ var configuration = new Dictionary
+ {
+ ["Seed"] = "false"
+ };
+ var sut = TestUtils.CreateTestServer(configurationOverrides: configuration);
+
+ sut.Services.GetRequiredService()
+ .SetTestUser(true, new Claim[] { new Claim("role", "Is4-Writer") });
+
+ var client = sut.CreateClient();
+
+ var registration = new ClientRegisteration
+ {
+ ClientNames = new List
+ {
+ new LocalizableProperty
+ {
+ Value = "test"
+ },
+ },
+ RedirectUris = new List
+ {
+ "http://localhost"
+ }
+ };
+
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+ var content = await response.Content.ReadAsStringAsync();
+ var result = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal(HttpStatusCode.Created, response.StatusCode);
+ Assert.NotNull(result.RegistrationToken);
+ Assert.NotNull(result.RegistrationUri);
+ }
+
+ registration.Jwks = new JsonWebKeys
+ {
+ Keys = new[]
+ {
+ new JsonWebKey
+ {
+ kty = "RSA",
+ e = "AQAB",
+ use = "sig",
+ alg = "RS256",
+ n = "qBulUDaYV027shwCq82LKIevXdQL2pCwXktQgf2TT3c496pxGdRuxcN_MHGKWNOGQsDLuAVk6NjxYF95obDUFrDiugMuXrvptPrTO8dzTX83k_6ngtjOtx2UrTk_7f0EYNrusykrsB-cOvCMREsfktlsavvMKBGrzpxaHlRxcSsMxzB0dddDSlH8mxlzOGcbBuvZnbNg0EUuQC4jvM9Gy6gUEcoU0S19XnUcgwLGLPfIX2dMO4FxTAsaaTYT7msxGMBNIVUTVnL0HctYr0YVYu0hD9rePnvxJ_-OwOdxIETQlR9vp61xFr4juzyyMWTrjCACxxLm-CyEQGjwx2YZaw"
+ }
+ }
+ };
+ registration.RedirectUris = new List
+ {
+ "https://localhost"
+ };
+ registration.ClientNames = new List
+ {
+ new LocalizableProperty
+ {
+ Culture = "fr-FR",
+ Value = "test"
+ },
+ };
+
+ registration.ClientUris = new List
+ {
+ new LocalizableProperty
+ {
+ Value = "https://localhost"
+ },
+ new LocalizableProperty
+ {
+ Culture = "fr-FR",
+ Value = "https://localhost/fr-FR"
+ },
+ };
+
+ registration.LogoUris = new List
+ {
+ new LocalizableProperty
+ {
+ Value = "https://localhost"
+ },
+ new LocalizableProperty
+ {
+ Culture = "fr-FR",
+ Value = "https://localhost/fr-FR"
+ },
+ };
+
+ registration.PolicyUris = new List
+ {
+ new LocalizableProperty
+ {
+ Value = "https://localhost"
+ },
+ new LocalizableProperty
+ {
+ Culture = "fr-FR",
+ Value = "https://localhost/fr-FR"
+ },
+ };
+
+ registration.TosUris = new List
+ {
+ new LocalizableProperty
+ {
+ Value = "https://localhost"
+ },
+ new LocalizableProperty
+ {
+ Culture = "fr-FR",
+ Value = "https://localhost/fr-FR"
+ },
+ };
+
+ registration.JwksUri = "https://jwk";
+
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+ Assert.Equal(HttpStatusCode.Created, response.StatusCode);
+ }
+ }
+
+
+ [Fact]
+ public async Task CreateAsync_should_validate_request()
+ {
+ var sut = TestUtils.CreateTestServer();
+ sut.Services.GetRequiredService()
+ .SetTestUser(true, new Claim[] { new Claim("role", "Is4-Writer") });
+
+ var client = sut.CreateClient();
+
+ var registration = new ClientRegisteration
+ {
+ };
+
+ // not redirect uri
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+
+ var content = await response.Content.ReadAsStringAsync();
+
+ var error = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal("invalid_redirect_uri", error.Error);
+
+ Assert.Equal("RedirectUri is required.", error.Error_description);
+ }
+
+ registration.RedirectUris = new List
+ {
+ };
+
+ // empty redirect uris
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+
+ var content = await response.Content.ReadAsStringAsync();
+
+ var error = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal("invalid_redirect_uri", error.Error);
+ Assert.Equal("RedirectUri is required.", error.Error_description);
+ }
+
+ registration.RedirectUris = new List
+ {
+ "test"
+ };
+
+ // invalid uri prefix
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+
+ var content = await response.Content.ReadAsStringAsync();
+
+ var error = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal("invalid_redirect_uri", error.Error);
+ Assert.Equal("RedirectUri 'test' is not valid.", error.Error_description);
+ }
+
+
+ registration.RedirectUris = new List
+ {
+ "ssh:test"
+ };
+
+ // invalid uri prefix
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+
+ var content = await response.Content.ReadAsStringAsync();
+
+ var error = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal("invalid_redirect_uri", error.Error);
+ Assert.Equal("RedirectUri 'ssh:test' uses invalid scheme. If this scheme should be allowed, then configure it via ValidationOptions.", error.Error_description);
+ }
+
+ registration.RedirectUris = new List
+ {
+ "http://localhost"
+ };
+ registration.LogoUris = new List
+ {
+ new LocalizableProperty
+ {
+ Value = "test"
+ }
+ };
+
+ // invalid logo uris
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+
+ var content = await response.Content.ReadAsStringAsync();
+
+ var error = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal("invalid_logo_uri", error.Error);
+ Assert.Equal("LogoUri 'test' is not valid.", error.Error_description);
+ }
+
+ registration.LogoUris = new List
+ {
+ new LocalizableProperty
+ {
+ Value = "http://test"
+ }
+ };
+
+ // logo uri don't match redirect uri host
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+
+ var content = await response.Content.ReadAsStringAsync();
+
+ var error = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal("invalid_logo_uri", error.Error);
+ Assert.Equal("LogoUri 'http://test' host doesn't match a redirect uri host.", error.Error_description);
+ }
+
+ registration.LogoUris = null;
+ registration.PolicyUris = new List
+ {
+ new LocalizableProperty
+ {
+ Value = "test"
+ }
+ };
+
+ // invalid logo uris
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+
+ var content = await response.Content.ReadAsStringAsync();
+
+ var error = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal("invalid_policy_uri", error.Error);
+ Assert.Equal("PolicyUri 'test' is not valid.", error.Error_description);
+ }
+
+ registration.PolicyUris = new List
+ {
+ new LocalizableProperty
+ {
+ Value = "http://test"
+ }
+ };
+
+ // policy uri don't match redirect uri host
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+
+ var content = await response.Content.ReadAsStringAsync();
+
+ var error = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal("invalid_policy_uri", error.Error);
+ Assert.Equal("PolicyUri 'http://test' host doesn't match a redirect uri host.", error.Error_description);
+ }
+
+ registration.PolicyUris = null;
+ registration.GrantTypes = new[] { "invalid" };
+
+ // invalid grant type
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+
+ var content = await response.Content.ReadAsStringAsync();
+
+ var error = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal("invalid_grant_type", error.Error);
+ Assert.Equal("GrantType 'invalid' is not supported.", error.Error_description);
+ }
+
+ registration.GrantTypes = null;
+ registration.ResponseTypes = new[] { "invalid" };
+
+ // invalid reponse type
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+
+ var content = await response.Content.ReadAsStringAsync();
+
+ var error = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal("invalid_response_type", error.Error);
+ Assert.Equal("ResponseType 'invalid' is not supported.", error.Error_description);
+ }
+
+ registration.GrantTypes = new[] { "implicit" };
+ registration.RedirectUris = new[]
+ {
+ "https://test"
+ };
+ registration.ResponseTypes = new[] { "code" };
+
+ // invalid reponse type
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+
+ var content = await response.Content.ReadAsStringAsync();
+
+ var error = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal("invalid_response_type", error.Error);
+ Assert.Equal("No GrantType 'authorization_code' for ResponseType 'code' found in grant_types.", error.Error_description);
+ }
+
+ registration.GrantTypes = null;
+ registration.ResponseTypes = new[] { "id_token" };
+
+ // reponse / grant type doesn't match
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+
+ var content = await response.Content.ReadAsStringAsync();
+
+ var error = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal("invalid_response_type", error.Error);
+ Assert.Equal("No GrantType 'implicit' for ResponseType 'id_token' found in grant_types.", error.Error_description);
+ }
+
+ registration.ResponseTypes = new[] { "token id_token" };
+
+ // reponse / grant type doesn't match
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+
+ var content = await response.Content.ReadAsStringAsync();
+
+ var error = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal("invalid_response_type", error.Error);
+ Assert.Equal("No GrantType 'implicit' for ResponseType 'token id_token' found in grant_types.", error.Error_description);
+ }
+
+ registration.ResponseTypes = new[] { "code token id_token" };
+ registration.GrantTypes = new[] { "implicit" };
+
+ // reponse / grant type doesn't match
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+
+ var content = await response.Content.ReadAsStringAsync();
+
+ var error = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal("invalid_response_type", error.Error);
+ Assert.Equal("No GrantType 'authorization_code' for ResponseType 'code token id_token' found in grant_types.", error.Error_description);
+ }
+
+ registration.ResponseTypes = new[] { "token" };
+ registration.RedirectUris = new[]
+ {
+ "http://test"
+ };
+ // invalid scheme for grant type implicit
+
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+
+ var content = await response.Content.ReadAsStringAsync();
+
+ var error = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal("invalid_redirect_uri", error.Error);
+ Assert.Equal("Invalid RedirectUri 'http://test'. Implicit client must use 'https' scheme only.", error.Error_description);
+ }
+
+ registration.RedirectUris = new[]
+ {
+ "https://localhost"
+ };
+ // invalid host for grant type implicit
+
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+
+ var content = await response.Content.ReadAsStringAsync();
+
+ var error = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal("invalid_redirect_uri", error.Error);
+ Assert.Equal("Invalid RedirectUri 'https://localhost'. Implicit client cannot use 'localhost' host.", error.Error_description);
+ }
+
+ registration.RedirectUris = new[]
+ {
+ "https://localhost"
+ };
+ // invalid host for grant type implicit
+
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+
+ var content = await response.Content.ReadAsStringAsync();
+
+ var error = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal("invalid_redirect_uri", error.Error);
+ Assert.Equal("Invalid RedirectUri 'https://localhost'. Implicit client cannot use 'localhost' host.", error.Error_description);
+ }
+
+ registration.ResponseTypes = null;
+ registration.GrantTypes = null;
+
+ registration.ApplicationType = "native";
+ registration.RedirectUris = new[]
+ {
+ "http://test"
+ };
+
+ // invalid host for native client
+
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+
+ var content = await response.Content.ReadAsStringAsync();
+
+ var error = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal("invalid_redirect_uri", error.Error);
+ Assert.Equal("Invalid RedirectUri 'http://test'.Only 'localhost' host is allowed for 'http' scheme and 'native' client.", error.Error_description);
+ }
+
+ registration.ApplicationType = "invalid";
+
+ // invalid application type
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+
+ var content = await response.Content.ReadAsStringAsync();
+
+ var error = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal("invalid_application_type", error.Error);
+ Assert.Equal("ApplicationType 'invalid' is invalid. It must be 'web' or 'native'.", error.Error_description);
+ }
+ }
+
+ [Theory]
+ [InlineData("gopher://test")]
+ [InlineData("https://test")]
+ [InlineData("news:test@test.com")]
+ [InlineData("nntp://test@test.com")]
+ public async Task CreateAsync_should_validate_native_redirect_uri_scheme(string redirectUri)
+ {
+ var sut = TestUtils.CreateTestServer();
+ sut.Services.GetRequiredService()
+ .SetTestUser(true, new Claim[] { new Claim("role", "Is4-Writer") });
+
+ var client = sut.CreateClient();
+
+ var registration = new ClientRegisteration
+ {
+ RedirectUris = new[]
+ {
+ redirectUri
+ },
+ ApplicationType = "native"
+ };
+
+ // not redirect uri
+ using var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json");
+ using var response = await client.PostAsync("/api/register", request);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+
+ var content = await response.Content.ReadAsStringAsync();
+
+ var error = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal("invalid_redirect_uri", error.Error);
+
+ var uri = new Uri(redirectUri);
+
+ Assert.Equal($"Invalid RedirectUri '{redirectUri}'.Native client cannot use standard '{uri.Scheme}' scheme, you must use a custom scheme such as 'net.pipe' or 'net.tcp', or 'http' scheme with 'localhost' host.", error.Error_description);
+ }
+
+ [Fact]
+ public async Task CreateAsync_should_validate_caller()
+ {
+ var sut = TestUtils.CreateTestServer(configurationOverrides: new Dictionary
+ {
+ ["DynamicClientRegistrationOptions:AllowedContacts:0:Contact"] = "test",
+ ["DynamicClientRegistrationOptions:AllowedContacts:0:AllowedHosts:0"] = "localhost",
+ });
+
+ var client = sut.CreateClient();
+
+ var registration = new ClientRegisteration
+ {
+ ClientNames = new List
+ {
+ new LocalizableProperty
+ {
+ Value = "test"
+ },
+ },
+ RedirectUris = new List
+ {
+ "http://localhost"
+ },
+ Contacts = new[]
+ {
+ "test"
+ }
+ };
+
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+ var content = await response.Content.ReadAsStringAsync();
+ var result = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal(HttpStatusCode.Created, response.StatusCode);
+ Assert.NotNull(result.RegistrationToken);
+ Assert.NotNull(result.RegistrationUri);
+ }
+
+ registration.RedirectUris = new[]
+ {
+ "http://forbidenn"
+ };
+
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+
+ Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
+ }
+
+
+ registration.Contacts = new[]
+ {
+ "forbidenn"
+ };
+
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+
+ Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
+ }
+ }
+
+
+ [Fact]
+ public async Task UpdateAsync_should_update_client()
+ {
+ var sut = TestUtils.CreateTestServer();
+ sut.Services.GetRequiredService()
+ .SetTestUser(true, new Claim[] { new Claim("role", "Is4-Writer") });
+
+ var client = sut.CreateClient();
+
+ var registration = new ClientRegisteration
+ {
+ ClientNames = new List
+ {
+ new LocalizableProperty
+ {
+ Value = "test"
+ },
+ },
+ RedirectUris = new List
+ {
+ "http://localhost"
+ }
+ };
+
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var response = await client.PostAsync("/api/register", request);
+
+ var content = await response.Content.ReadAsStringAsync();
+ registration = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal(HttpStatusCode.Created, response.StatusCode);
+ Assert.NotNull(registration.RegistrationToken);
+ }
+
+ using (var request = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json"))
+ {
+ using var message = new HttpRequestMessage
+ {
+ Method = HttpMethod.Put,
+ Content = request,
+ RequestUri = new Uri(registration.RegistrationUri)
+ };
+ message.Headers.Authorization = new AuthenticationHeaderValue("Bearer", registration.RegistrationToken);
+
+ using var response = await client.SendAsync(message);
+
+ var content = await response.Content.ReadAsStringAsync();
+ var result = JsonConvert.DeserializeObject(content);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ Assert.Null(result.RegistrationToken);
+ Assert.Null(result.RegistrationUri);
+ }
+
+ registration.RedirectUris = new List