@@ -12,6 +12,7 @@ namespace Teknik.Configuration | |||
public string ClientSecret { get; set; } | |||
public List<string> RedirectUris { get; set; } | |||
public List<string> PostLogoutRedirectUris { get; set; } | |||
public List<string> AllowedCorsOrigins { get; set; } | |||
public string APIName { get; set; } | |||
public string APISecret { get; set; } | |||
@@ -23,6 +24,7 @@ namespace Teknik.Configuration | |||
ClientSecret = "mysecret"; | |||
RedirectUris = new List<string>(); | |||
PostLogoutRedirectUris = new List<string>(); | |||
AllowedCorsOrigins = new List<string>(); | |||
APIName = "api"; | |||
APISecret = "secret"; | |||
} |
@@ -295,7 +295,18 @@ namespace Teknik.IdentityServer.Controllers | |||
} | |||
return View("LoggedOut", vm); | |||
return RedirectToLocal(model.ReturnURL); | |||
} | |||
[HttpOptions] | |||
public async Task Logout() | |||
{ | |||
if (User?.Identity.IsAuthenticated == true) | |||
{ | |||
await _signInManager.SignOutAsync(); | |||
// raise the logout event | |||
await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName())); | |||
} | |||
} | |||
private IActionResult RedirectToLocal(string returnUrl) |
@@ -69,7 +69,7 @@ namespace Teknik.IdentityServer.Controllers | |||
} | |||
[HttpPost] | |||
public async Task<IActionResult> DeleteUser(DeleteUserModel model) | |||
public async Task<IActionResult> DeleteUser(DeleteUserModel model, [FromServices] ConfigurationDbContext configContext) | |||
{ | |||
if (string.IsNullOrEmpty(model.Username)) | |||
return new JsonResult(new { success = false, message = "Username is required" }); | |||
@@ -77,6 +77,18 @@ namespace Teknik.IdentityServer.Controllers | |||
var foundUser = await _userManager.FindByNameAsync(model.Username); | |||
if (foundUser != null) | |||
{ | |||
// Find this user's clients | |||
var foundClients = configContext.Clients.Where(c => | |||
c.Properties.Exists(p => | |||
p.Key == "username" && | |||
p.Value.ToLower() == model.Username.ToLower()) | |||
).ToList(); | |||
if (foundClients != null) | |||
{ | |||
configContext.Clients.RemoveRange(foundClients); | |||
configContext.SaveChanges(); | |||
} | |||
var result = await _userManager.DeleteAsync(foundUser); | |||
if (result.Succeeded) | |||
return new JsonResult(new { success = true }); | |||
@@ -467,6 +479,10 @@ namespace Teknik.IdentityServer.Controllers | |||
var clientSecret = StringHelper.RandomString(40, "abcdefghjkmnpqrstuvwxyz1234567890"); | |||
// Generate the origin for the callback | |||
Uri redirect = new Uri(model.CallbackUrl); | |||
string origin = redirect.Scheme + "://" + redirect.Host; | |||
var client = new IdentityServer4.Models.Client | |||
{ | |||
Properties = new Dictionary<string, string>() | |||
@@ -495,6 +511,11 @@ namespace Teknik.IdentityServer.Controllers | |||
model.CallbackUrl | |||
}, | |||
AllowedCorsOrigins = | |||
{ | |||
origin | |||
}, | |||
AllowedScopes = model.AllowedScopes, | |||
AllowOfflineAccess = true | |||
@@ -530,6 +551,22 @@ namespace Teknik.IdentityServer.Controllers | |||
newUri.RedirectUri = model.CallbackUrl; | |||
configContext.Add(newUri); | |||
// Generate the origin for the callback | |||
Uri redirect = new Uri(model.CallbackUrl); | |||
string origin = redirect.Scheme + "://" + redirect.Host; | |||
// Update the allowed origin for this client | |||
var corsOrigins = configContext.Set<ClientCorsOrigin>().Where(c => c.ClientId == foundClient.Id).ToList(); | |||
if (corsOrigins != null) | |||
{ | |||
configContext.RemoveRange(corsOrigins); | |||
} | |||
var newOrigin = new ClientCorsOrigin(); | |||
newOrigin.Client = foundClient; | |||
newOrigin.ClientId = foundClient.Id; | |||
newOrigin.Origin = origin; | |||
configContext.Add(newUri); | |||
// Save all the changed | |||
configContext.SaveChanges(); | |||
@@ -32,6 +32,13 @@ | |||
<ProjectReference Include="..\Logging\Logging.csproj" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<Content Update="App_Data\Config.json"> | |||
<CopyToPublishDirectory>Never</CopyToPublishDirectory> | |||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | |||
</Content> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<None Update="Images\favicon.ico"> | |||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> |
@@ -31,23 +31,24 @@ namespace Teknik.IdentityServer.Middleware | |||
if (!string.IsNullOrEmpty(host)) | |||
{ | |||
allowedDomain = host; | |||
} | |||
string domain = host.GetDomain(); | |||
allowedDomain = string.Format("*.{0} {0}", domain); | |||
} | |||
var csp = string.Format( | |||
"default-src 'none'; " + | |||
"script-src blob: 'unsafe-eval' 'nonce-{1}' {0}; " + | |||
"default-src 'self'; " + | |||
"script-src blob: 'unsafe-eval' 'unsafe-inline' {0}; " + | |||
"style-src 'unsafe-inline' {0}; " + | |||
"img-src data: *; " + | |||
"font-src data: {0}; " + | |||
"connect-src wss: blob: data: {0}; " + | |||
"media-src *; " + | |||
"worker-src blob: mediastream: {0}; " + | |||
"form-action {0}; " + | |||
"form-action *; " + | |||
"base-uri {0}; " + | |||
"frame-ancestors {0};", | |||
allowedDomain, | |||
httpContext.Items[Constants.NONCE_KEY]); | |||
allowedDomain); | |||
if (!httpContext.Response.Headers.ContainsKey("Content-Security-Policy")) | |||
{ |
@@ -35,14 +35,6 @@ namespace Teknik.IdentityServer.Middleware | |||
// Content Type Options | |||
headers.Append("X-Content-Type-Options", "nosniff"); | |||
// Public Key Pinning | |||
string keys = string.Empty; | |||
foreach (string key in config.PublicKeys) | |||
{ | |||
keys += $"pin-sha256=\"{key}\";"; | |||
} | |||
headers.Append("Public-Key-Pins", $"max-age=300; includeSubDomains; {keys}"); | |||
// Referrer Policy | |||
headers.Append("Referrer-Policy", "no-referrer, strict-origin-when-cross-origin"); | |||
@@ -10,7 +10,7 @@ by editing this MSBuild file. In order to learn more about this please visit htt | |||
<LastUsedPlatform>Any CPU</LastUsedPlatform> | |||
<SiteUrlToLaunchAfterPublish>https://authdev.teknik.io</SiteUrlToLaunchAfterPublish> | |||
<LaunchSiteAfterPublish>True</LaunchSiteAfterPublish> | |||
<ExcludeApp_Data>False</ExcludeApp_Data> | |||
<ExcludeApp_Data>True</ExcludeApp_Data> | |||
<TargetFramework>netcoreapp2.1</TargetFramework> | |||
<ProjectGuid>05842e03-223a-4f43-9e81-d968a9475a97</ProjectGuid> | |||
<SelfContained>false</SelfContained> |
@@ -23,6 +23,7 @@ using Teknik.Logging; | |||
using Microsoft.AspNetCore.Authorization; | |||
using Teknik.IdentityServer.Models; | |||
using IdentityServer4.Services; | |||
using System.Collections.Generic; | |||
namespace Teknik.IdentityServer | |||
{ | |||
@@ -108,6 +109,10 @@ namespace Teknik.IdentityServer | |||
options.Events.RaiseSuccessEvents = true; | |||
options.UserInteraction.ErrorUrl = "/Error/IdentityError"; | |||
options.UserInteraction.ErrorIdParameter = "errorId"; | |||
options.Cors.CorsPaths.Add(new PathString("/connect/authorize")); | |||
options.Cors.CorsPaths.Add(new PathString("/connect/endsession")); | |||
options.Cors.CorsPaths.Add(new PathString("/connect/checksession")); | |||
options.Cors.CorsPaths.Add(new PathString("/connect/introspect")); | |||
}) | |||
.AddOperationalStore(options => | |||
options.ConfigureDbContext = builder => |
@@ -1208,6 +1208,8 @@ namespace Teknik.Areas.Users.Controllers | |||
return Json(new { error = "You must enter a client name" }); | |||
if (string.IsNullOrEmpty(callbackUrl)) | |||
return Json(new { error = "You must enter an authorization callback URL" }); | |||
if (!callbackUrl.IsValidUrl()) | |||
return Json(new { error = "Invalid callback URL" }); | |||
// Validate the code with the identity server | |||
var result = await IdentityHelper.CreateClient(_config, User.Identity.Name, name, homepageUrl, logoUrl, callbackUrl, "openid", "role", "account-info", "security-info", "teknik-api.read", "teknik-api.write"); | |||
@@ -1267,6 +1269,8 @@ namespace Teknik.Areas.Users.Controllers | |||
return Json(new { error = "You must enter a client name" }); | |||
if (string.IsNullOrEmpty(callbackUrl)) | |||
return Json(new { error = "You must enter an authorization callback URL" }); | |||
if (!callbackUrl.IsValidUrl()) | |||
return Json(new { error = "Invalid callback URL" }); | |||
Client foundClient = await IdentityHelper.GetClient(_config, User.Identity.Name, clientId); | |||
@@ -42,6 +42,4 @@ | |||
</div> | |||
</div> | |||
} | |||
</div> | |||
<bundle src="js/user.settings.min.js" append-version="true"></bundle> | |||
</div> |
@@ -35,14 +35,6 @@ namespace Teknik.Middleware | |||
// Content Type Options | |||
headers.Append("X-Content-Type-Options", "nosniff"); | |||
// Public Key Pinning | |||
string keys = string.Empty; | |||
foreach (string key in config.PublicKeys) | |||
{ | |||
keys += $"pin-sha256=\"{key}\";"; | |||
} | |||
headers.Append("Public-Key-Pins", $"max-age=300; includeSubDomains; {keys}"); | |||
// Referrer Policy | |||
headers.Append("Referrer-Policy", "no-referrer, strict-origin-when-cross-origin"); | |||
@@ -3,7 +3,7 @@ | |||
"windowsAuthentication": false, | |||
"anonymousAuthentication": true, | |||
"iisExpress": { | |||
"applicationUrl": "http://localhost:23818", | |||
"applicationUrl": "https://localhost:23818", | |||
"sslPort": 44362 | |||
} | |||
}, | |||
@@ -21,7 +21,7 @@ | |||
"launchBrowser": true, | |||
"launchUrl": "?sub=www", | |||
"environmentVariables": { | |||
"ASPNETCORE_URLS": "http://localhost:5050", | |||
"ASPNETCORE_URLS": "https://localhost:5050", | |||
"ASPNETCORE_ENVIRONMENT": "Development" | |||
} | |||
}, | |||
@@ -30,7 +30,7 @@ | |||
"launchBrowser": true, | |||
"launchUrl": "?sub=www", | |||
"environmentVariables": { | |||
"ASPNETCORE_URLS": "http://localhost:5050", | |||
"ASPNETCORE_URLS": "https://localhost:5050", | |||
"ASPNETCORE_ENVIRONMENT": "Production" | |||
} | |||
} |
@@ -44,14 +44,8 @@ $(document).ready(function () { | |||
type: "POST", | |||
url: deleteUserURL, | |||
data: AddAntiForgeryToken({}), | |||
success: function (response) { | |||
if (response.result) { | |||
window.location.replace(homeUrl); | |||
} | |||
else { | |||
$("#top_msg").css('display', 'inline', 'important'); | |||
$("#top_msg").html('<div class="alert alert-danger alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>' + parseErrorMessage(response) + '</div>'); | |||
} | |||
success: function () { | |||
window.location.replace(homeUrl); | |||
}, | |||
error: function (response) { | |||
$("#top_msg").css('display', 'inline', 'important'); |
@@ -77,6 +77,11 @@ namespace Teknik | |||
// Resolve the services from the service provider | |||
var config = sp.GetService<Config>(); | |||
if (config.DevEnvironment) | |||
{ | |||
Environment.EnvironmentName = EnvironmentName.Development; | |||
} | |||
// Add Tracking Filter scopes | |||
//services.AddScoped<TrackDownload>(); | |||
//services.AddScoped<TrackLink>(); | |||
@@ -264,13 +269,6 @@ namespace Teknik | |||
// Use Exception Handling | |||
app.UseErrorHandler(config); | |||
if (env.IsDevelopment()) | |||
{ | |||
app.UseBrowserLink(); | |||
//app.UseDeveloperExceptionPage(); | |||
app.UseDatabaseErrorPage(); | |||
} | |||
// Performance Monitor the entire request | |||
app.UsePerformanceMonitor(); | |||
@@ -49,12 +49,19 @@ namespace Teknik.Utilities | |||
// If the param is not being used, we will use the curSub | |||
if (string.IsNullOrEmpty(subParam)) | |||
{ | |||
// If we are on dev and no subparam, we need to set the subparam to the specified sub | |||
subParam = (curSub == "dev") ? sub : string.Empty; | |||
string firstSub = (curSub == "dev") ? "dev" : sub; | |||
if (!string.IsNullOrEmpty(firstSub)) | |||
if (url.ActionContext.HttpContext.Request.IsLocal()) | |||
{ | |||
domain = firstSub + "." + domain; | |||
subParam = sub; | |||
} | |||
else | |||
{ | |||
// If we are on dev and no subparam, we need to set the subparam to the specified sub | |||
subParam = (curSub == "dev") ? sub : string.Empty; | |||
string firstSub = (curSub == "dev") ? "dev" : sub; | |||
if (!string.IsNullOrEmpty(firstSub)) | |||
{ | |||
domain = firstSub + "." + domain; | |||
} | |||
} | |||
} | |||
else |