@@ -12,6 +12,8 @@ using IdentityServer4.Stores; | |||
using Microsoft.AspNetCore.Authorization; | |||
using Microsoft.AspNetCore.Identity; | |||
using Microsoft.AspNetCore.Mvc; | |||
using Microsoft.EntityFrameworkCore; | |||
using Microsoft.EntityFrameworkCore.Internal; | |||
using Microsoft.Extensions.Logging; | |||
using Teknik.Configuration; | |||
using Teknik.IdentityServer.Models; | |||
@@ -473,6 +475,8 @@ namespace Teknik.IdentityServer.Controllers | |||
}, | |||
ClientId = clientId, | |||
ClientName = model.Name, | |||
ClientUri = model.HomepageUrl, | |||
LogoUri = model.LogoUrl, | |||
AllowedGrantTypes = new List<string>() | |||
{ | |||
GrantType.AuthorizationCode, | |||
@@ -480,20 +484,15 @@ namespace Teknik.IdentityServer.Controllers | |||
}, | |||
ClientSecrets = | |||
{ | |||
new IdentityServer4.Models.Secret(clientSecret.Sha256()) | |||
}, | |||
{ | |||
new IdentityServer4.Models.Secret(clientSecret.Sha256()) | |||
}, | |||
RequireConsent = true, | |||
RedirectUris = | |||
{ | |||
model.RedirectURI | |||
}, | |||
PostLogoutRedirectUris = | |||
{ | |||
model.PostLogoutRedirectURI | |||
model.CallbackUrl | |||
}, | |||
AllowedScopes = model.AllowedScopes, | |||
@@ -507,6 +506,39 @@ namespace Teknik.IdentityServer.Controllers | |||
return new JsonResult(new { success = true, data = new { id = clientId, secret = clientSecret } }); | |||
} | |||
[HttpPost] | |||
public IActionResult EditClient(EditClientModel model, [FromServices] ConfigurationDbContext configContext) | |||
{ | |||
// Validate it's an actual client | |||
var foundClient = configContext.Clients.Where(c => c.ClientId == model.ClientId).FirstOrDefault(); | |||
if (foundClient != null) | |||
{ | |||
foundClient.ClientName = model.Name; | |||
foundClient.ClientUri = model.HomepageUrl; | |||
foundClient.LogoUri = model.LogoUrl; | |||
configContext.Entry(foundClient).State = EntityState.Modified; | |||
// Update the redirect URL for this client | |||
var results = configContext.Set<ClientRedirectUri>().Where(c => c.ClientId == foundClient.Id).ToList(); | |||
if (results != null) | |||
{ | |||
configContext.RemoveRange(results); | |||
} | |||
var newUri = new ClientRedirectUri(); | |||
newUri.Client = foundClient; | |||
newUri.ClientId = foundClient.Id; | |||
newUri.RedirectUri = model.CallbackUrl; | |||
configContext.Add(newUri); | |||
// Save all the changed | |||
configContext.SaveChanges(); | |||
return new JsonResult(new { success = true }); | |||
} | |||
return new JsonResult(new { success = false, message = "Client does not exist." }); | |||
} | |||
[HttpPost] | |||
public IActionResult DeleteClient(DeleteClientModel model, [FromServices] ConfigurationDbContext configContext) | |||
{ |
@@ -9,8 +9,9 @@ namespace Teknik.IdentityServer.Models.Manage | |||
{ | |||
public string Username { get; set; } | |||
public string Name { get; set; } | |||
public string RedirectURI { get; set; } | |||
public string PostLogoutRedirectURI { get; set; } | |||
public string HomepageUrl { get; set; } | |||
public string LogoUrl { get; set; } | |||
public string CallbackUrl { get; set; } | |||
public ICollection<string> AllowedScopes { get; set; } | |||
} | |||
} |
@@ -0,0 +1,17 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace Teknik.IdentityServer.Models.Manage | |||
{ | |||
public class EditClientModel | |||
{ | |||
public string Username { get; set; } | |||
public string ClientId { get; set; } | |||
public string Name { get; set; } | |||
public string HomepageUrl { get; set; } | |||
public string LogoUrl { get; set; } | |||
public string CallbackUrl { get; set; } | |||
} | |||
} |
@@ -22,7 +22,7 @@ | |||
@if (Model.SignOutIframeUrl != null) | |||
{ | |||
<iframe width="0" height="0" class="signout" src="@Model.SignOutIframeUrl"></iframe> | |||
<iframe width="0" height="0" style="width:0; height:0; border:0; border:none" class="signout" src="@Model.SignOutIframeUrl"></iframe> | |||
} | |||
</div> | |||
</div> |
@@ -5,10 +5,10 @@ | |||
<div class="col-sm-12 text-center"> | |||
<div class="page-consent"> | |||
<div class="row page-header"> | |||
<div class="col-sm-10 col-sm-offset-1"> | |||
<div class="col-md-6 col-md-offset-3"> | |||
@if (Model.ClientLogoUrl != null) | |||
{ | |||
<div class="client-logo"><img src="@Model.ClientLogoUrl"></div> | |||
<div class="client-logo"><img src="@Model.ClientLogoUrl" style="max-height: 100px; max-width: 100px;"></div> | |||
} | |||
<h1> | |||
@Model.ClientName | |||
@@ -18,7 +18,7 @@ | |||
</div> | |||
<div class="row"> | |||
<div class="col-sm-10 col-sm-offset-1"> | |||
<div class="col-md-4 col-md-offset-4"> | |||
@await Html.PartialAsync("_ValidationSummary") | |||
<form asp-action="Index" class="consent-form"> |
@@ -31,7 +31,7 @@ | |||
<div class="col-sm-2"> | |||
@if (grant.ClientLogoUrl != null) | |||
{ | |||
<img src="@grant.ClientLogoUrl"> | |||
<img src="@grant.ClientLogoUrl" style="max-height: 100px; max-width: 100px;"> | |||
} | |||
</div> | |||
<div class="col-sm-8"> |
@@ -17,7 +17,7 @@ using Teknik.Utilities; | |||
namespace Teknik.Areas.API.V1.Controllers | |||
{ | |||
[Authorize(AuthenticationSchemes = "Bearer", Policy = "AnyAPI")] | |||
[Authorize(AuthenticationSchemes = "Bearer", Policy = "WriteOnlyAPI")] | |||
public class PasteAPIv1Controller : APIv1Controller | |||
{ | |||
public PasteAPIv1Controller(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { } |
@@ -18,7 +18,7 @@ using Teknik.Utilities; | |||
namespace Teknik.Areas.API.V1.Controllers | |||
{ | |||
[Authorize(AuthenticationSchemes = "Bearer", Policy = "AnyAPI")] | |||
[Authorize(AuthenticationSchemes = "Bearer", Policy = "WriteOnlyAPI")] | |||
public class ShortenAPIv1Controller : APIv1Controller | |||
{ | |||
public ShortenAPIv1Controller(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { } |
@@ -22,7 +22,7 @@ using Teknik.Utilities; | |||
namespace Teknik.Areas.API.V1.Controllers | |||
{ | |||
[Authorize(AuthenticationSchemes = "Bearer", Policy = "AnyAPI")] | |||
[Authorize(AuthenticationSchemes = "Bearer", Policy = "WriteOnlyAPI")] | |||
public class UploadAPIv1Controller : APIv1Controller | |||
{ | |||
public UploadAPIv1Controller(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { } |
@@ -406,8 +406,9 @@ namespace Teknik.Areas.Users.Controllers | |||
{ | |||
Id = client.ClientId, | |||
Name = client.ClientName, | |||
RedirectURI = string.Join(',', client.RedirectUris), | |||
PostLogoutRedirectURI = string.Join(',', client.PostLogoutRedirectUris), | |||
HomepageUrl = client.ClientUri, | |||
LogoUrl = client.LogoUri, | |||
CallbackUrl = string.Join(',', client.RedirectUris), | |||
AllowedScopes = client.AllowedScopes | |||
}); | |||
} | |||
@@ -1197,12 +1198,17 @@ namespace Teknik.Areas.Users.Controllers | |||
[HttpPost] | |||
[ValidateAntiForgeryToken] | |||
public async Task<IActionResult> CreateClient(string name, string redirectUri, string logoutUri, [FromServices] ICompositeViewEngine viewEngine) | |||
public async Task<IActionResult> CreateClient(string name, string homepageUrl, string logoUrl, string callbackUrl, [FromServices] ICompositeViewEngine viewEngine) | |||
{ | |||
try | |||
{ | |||
if (string.IsNullOrEmpty(name)) | |||
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" }); | |||
// Validate the code with the identity server | |||
var result = await IdentityHelper.CreateClient(_config, User.Identity.Name, name, redirectUri, logoutUri, "openid", "role", "account-info", "security-info", "teknik-api.read", "teknik-api.write"); | |||
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"); | |||
if (result.Success) | |||
{ | |||
@@ -1211,8 +1217,9 @@ namespace Teknik.Areas.Users.Controllers | |||
ClientViewModel model = new ClientViewModel(); | |||
model.Id = client["id"].ToString(); | |||
model.Name = name; | |||
model.RedirectURI = redirectUri; | |||
model.PostLogoutRedirectURI = logoutUri; | |||
model.HomepageUrl = homepageUrl; | |||
model.LogoUrl = logoUrl; | |||
model.CallbackUrl = callbackUrl; | |||
string renderedView = await RenderPartialViewToString(viewEngine, "~/Areas/User/Views/User/Settings/ClientView.cshtml", model); | |||
@@ -1226,6 +1233,70 @@ namespace Teknik.Areas.Users.Controllers | |||
} | |||
} | |||
[HttpPost] | |||
[ValidateAntiForgeryToken] | |||
public async Task<IActionResult> GetClient(string clientId) | |||
{ | |||
Client foundClient = await IdentityHelper.GetClient(_config, User.Identity.Name, clientId); | |||
if (foundClient != null) | |||
{ | |||
ClientViewModel model = new ClientViewModel() | |||
{ | |||
Id = foundClient.ClientId, | |||
Name = foundClient.ClientName, | |||
HomepageUrl = foundClient.ClientUri, | |||
LogoUrl = foundClient.LogoUri, | |||
CallbackUrl = string.Join(',', foundClient.RedirectUris), | |||
AllowedScopes = foundClient.AllowedScopes | |||
}; | |||
return Json(new { result = true, client = model }); | |||
} | |||
return new StatusCodeResult(StatusCodes.Status403Forbidden); | |||
} | |||
[HttpPost] | |||
[ValidateAntiForgeryToken] | |||
public async Task<IActionResult> EditClient(string clientId, string name, string homepageUrl, string logoUrl, string callbackUrl, [FromServices] ICompositeViewEngine viewEngine) | |||
{ | |||
try | |||
{ | |||
if (string.IsNullOrEmpty(name)) | |||
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" }); | |||
Client foundClient = await IdentityHelper.GetClient(_config, User.Identity.Name, clientId); | |||
if (foundClient == null) | |||
return Json(new { error = "Client does not exist" }); | |||
// Validate the code with the identity server | |||
var result = await IdentityHelper.EditClient(_config, User.Identity.Name, clientId, name, homepageUrl, logoUrl, callbackUrl); | |||
if (result.Success) | |||
{ | |||
var client = (JObject)result.Data; | |||
ClientViewModel model = new ClientViewModel(); | |||
model.Id = clientId; | |||
model.Name = name; | |||
model.HomepageUrl = homepageUrl; | |||
model.LogoUrl = logoUrl; | |||
model.CallbackUrl = callbackUrl; | |||
string renderedView = await RenderPartialViewToString(viewEngine, "~/Areas/User/Views/User/Settings/ClientView.cshtml", model); | |||
return Json(new { result = true, clientId = clientId, html = renderedView }); | |||
} | |||
return Json(new { error = result.Message }); | |||
} | |||
catch (Exception ex) | |||
{ | |||
return Json(new { error = ex.GetFullMessage(true) }); | |||
} | |||
} | |||
[HttpPost] | |||
[ValidateAntiForgeryToken] | |||
public async Task<IActionResult> DeleteClient(string clientId) |
@@ -333,7 +333,7 @@ namespace Teknik.Areas.Users.Utility | |||
var result = await Get(config, manageUrl); | |||
if (result.Success) | |||
{ | |||
return (Client)result.Data; | |||
return ((JObject)result.Data).ToObject<Client>(); | |||
} | |||
throw new Exception(result.Message); | |||
} | |||
@@ -350,7 +350,7 @@ namespace Teknik.Areas.Users.Utility | |||
throw new Exception(result.Message); | |||
} | |||
public static async Task<IdentityResult> CreateClient(Config config, string username, string name, string redirectURI, string postLogoutRedirectURI, params string[] allowedScopes) | |||
public static async Task<IdentityResult> CreateClient(Config config, string username, string name, string homepageUrl, string logoUrl, string callbackUrl, params string[] allowedScopes) | |||
{ | |||
var manageUrl = CreateUrl(config, $"Manage/CreateClient"); | |||
@@ -359,13 +359,31 @@ namespace Teknik.Areas.Users.Utility | |||
{ | |||
username = username, | |||
name = name, | |||
redirectURI = redirectURI, | |||
postLogoutRedirectURI = postLogoutRedirectURI, | |||
homepageUrl = homepageUrl, | |||
logoUrl = logoUrl, | |||
callbackUrl = callbackUrl, | |||
allowedScopes = allowedScopes | |||
}); | |||
return response; | |||
} | |||
public static async Task<IdentityResult> EditClient(Config config, string username, string clientId, string name, string homepageUrl, string logoUrl, string callbackUrl) | |||
{ | |||
var manageUrl = CreateUrl(config, $"Manage/EditClient"); | |||
var response = await Post(config, manageUrl, | |||
new | |||
{ | |||
username = username, | |||
clientId = clientId, | |||
name = name, | |||
homepageUrl = homepageUrl, | |||
logoUrl = logoUrl, | |||
callbackUrl = callbackUrl | |||
}); | |||
return response; | |||
} | |||
public static async Task<IdentityResult> DeleteClient(Config config, string clientId) | |||
{ | |||
var manageUrl = CreateUrl(config, $"Manage/DeleteClient"); |
@@ -10,8 +10,9 @@ namespace Teknik.Areas.Users.ViewModels | |||
{ | |||
public string Id { get; set; } | |||
public string Name { get; set; } | |||
public string RedirectURI { get; set; } | |||
public string PostLogoutRedirectURI { get; set; } | |||
public string HomepageUrl { get; set; } | |||
public string LogoUrl { get; set; } | |||
public string CallbackUrl { get; set; } | |||
public ICollection<string> AllowedScopes { get; set; } | |||
} | |||
} |
@@ -15,23 +15,23 @@ | |||
<!form id="registrationForm" action="@Url.SubRouteUrl("user", "User.Register")" method="post" accept-charset="UTF-8"> | |||
<input name="Register.ReturnUrl" id="registerReturnUrl" type="hidden" value="@Model.ReturnUrl" /> | |||
<div class="form-group"> | |||
<label for="registerUsername">Username</label> | |||
<label for="registerUsername">Username <span class="text-danger">*</span></label> | |||
<input type="text" class="form-control" id="registerUsername" value="" placeholder="Bob" name="Register.Username" data-val-required="The Username field is required." data-val="true"/> | |||
</div> | |||
<div class="form-group"> | |||
<label for="registerPassword">Password</label> | |||
<label for="registerPassword">Password <span class="text-danger">*</span></label> | |||
<input type="password" class="form-control" id="registerPassword" value="" placeholder="********" name="Register.Password" data-val-required="The Password field is required." data-val="true"/> | |||
</div> | |||
<div class="form-group"> | |||
<label for="registerConfirmPassword">Confirm Password</label> | |||
<label for="registerConfirmPassword">Confirm Password <span class="text-danger">*</span></label> | |||
<input type="password" class="form-control" id="registerConfirmPassword" value="" placeholder="********" name="Register.ConfirmPassword" data-val-required="The Confirm Password field is required." data-val="true"/> | |||
</div> | |||
<div class="form-group"> | |||
<label for="registerInviteCode">Invite Code@(Config.UserConfig.InviteCodeRequired ? string.Empty : " (Optional)")</label> | |||
<label for="registerInviteCode">Invite Code@(Config.UserConfig.InviteCodeRequired ? " <span class=\"text-danger\">*</span>" : string.Empty)</label> | |||
<input type="text" class="form-control" id="registerInviteCode" value="@Model.InviteCode" placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" name="Register.InviteCode"/> | |||
</div> | |||
<div class="form-group"> | |||
<label for="registerRecoveryEmail">Recovery Email (Optional)</label> | |||
<label for="registerRecoveryEmail">Recovery Email</label> | |||
<input type="text" class="form-control" id="registerRecoveryEmail" value="" placeholder="user@example.com" name="Register.RecoveryEmail"/> | |||
</div> | |||
<p class="text-center"> |
@@ -2,22 +2,53 @@ | |||
<li class="list-group-item" id="client_@Model.Id"> | |||
<div class="btn-group btn-group-sm pull-right" role="group" aria-label="..."> | |||
<button type="button" class="btn btn-primary text-primary editClient" id="editClient_@Model.Id" data-clientId="@Model.Id">Edit</button> | |||
<button type="button" class="btn btn-danger text-danger deleteClient" id="deleteClient_@Model.Id" data-clientId="@Model.Id">Delete</button> | |||
</div> | |||
<h3 class="list-group-item-heading" id="clientName_@Model.Name">@Model.Name</h3> | |||
<h3 class="list-group-item-heading" id="clientName_@Model.Name"> | |||
@Model.Name | |||
@if (!string.IsNullOrEmpty(Model.LogoUrl)) | |||
{ | |||
<img src="@Model.LogoUrl" class="img-thumbnail" style="max-height: 100px; max-width: 100px;" /> | |||
} | |||
</h3> | |||
<hr /> | |||
<div class="list-group-item-text"> | |||
<div class="row"> | |||
<div class="col-sm-4"> | |||
<strong>Client Id</strong>: @Model.Id | |||
<div class="row"> | |||
<div class="col-sm-12"> | |||
<strong>Client Id</strong> | |||
</div> | |||
</div> | |||
<div class="row"> | |||
<div class="col-sm-12"> | |||
@Model.Id | |||
</div> | |||
</div> | |||
</div> | |||
<div class="col-sm-8"> | |||
<div class="col-sm-4"> | |||
<div class="row"> | |||
<div class="col-sm-12"> | |||
<strong>Homepage Url</strong> | |||
</div> | |||
</div> | |||
<div class="row"> | |||
<div class="col-sm-12"> | |||
<a href="@Model.HomepageUrl" target="_blank">@Model.HomepageUrl</a> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="col-sm-4"> | |||
<div class="row"> | |||
<div class="col-sm-4"><strong>Redirect Url</strong></div> | |||
<div class="col-sm-8">@Model.RedirectURI</div> | |||
<div class="col-sm-12"> | |||
<strong>Authorization Callback Url</strong> | |||
</div> | |||
</div> | |||
<div class="row"> | |||
<div class="col-sm-4"><strong>Logout Url</strong></div> | |||
<div class="col-sm-8">@Model.PostLogoutRedirectURI</div> | |||
<div class="col-sm-12"> | |||
@Model.CallbackUrl | |||
</div> | |||
</div> | |||
</div> | |||
</div> |
@@ -7,46 +7,12 @@ | |||
} | |||
<script> | |||
var generateTokenURL = '@Url.SubRouteUrl("user", "User.Action", new { action = "GenerateToken" })'; | |||
var revokeAllTokensURL = '@Url.SubRouteUrl("user", "User.Action", new { action = "RevokeAllTokens" })'; | |||
var editTokenNameURL = '@Url.SubRouteUrl("user", "User.Action", new { action = "EditTokenName" })'; | |||
var deleteTokenURL = '@Url.SubRouteUrl("user", "User.Action", new { action = "DeleteToken" })'; | |||
var createClientURL = '@Url.SubRouteUrl("user", "User.Action", new { action = "CreateClient" })'; | |||
var editClientURL = '@Url.SubRouteUrl("user", "User.Action", new { action = "EditClient" })'; | |||
var deleteClientURL = '@Url.SubRouteUrl("user", "User.Action", new { action = "DeleteClient" })'; | |||
var getClientURL = '@Url.SubRouteUrl("user", "User.Action", new { action = "GetClient" })'; | |||
</script> | |||
<!-- | |||
<div class="row"> | |||
<div class="col-sm-12"> | |||
<h2>API Clients</h2> | |||
<hr /> | |||
</div> | |||
</div> | |||
<div class="row"> | |||
<div class="col-sm-12"> | |||
<br /> | |||
<label for="authTokens"><h4>API Clients</h4></label><span class="pull-right"><button type="button" class="btn btn-default" id="create_authToken">Create Auth Token</button> <button type="button" class="btn btn-danger" id="revoke_all_authTokens">Revoke All</button></span> | |||
<div id="authTokens" style="overflow-y: auto; max-height: 400px;"> | |||
<ul class="list-group" id="apiClientList"> | |||
@if (Model.AuthTokens.Any()) | |||
{ | |||
foreach (AuthTokenViewModel token in Model.AuthTokens) | |||
{ | |||
@await Html.PartialAsync("Settings/AuthToken", token) | |||
} | |||
} | |||
else | |||
{ | |||
<li class="list-group-item text-center" id="noAuthTokens">No Authentication Tokens</li> | |||
} | |||
</ul> | |||
</div> | |||
</div> | |||
</div> | |||
--> | |||
<div class="row"> | |||
<div class="col-sm-12"> | |||
<h2>Clients</h2> | |||
@@ -56,8 +22,8 @@ | |||
<div class="row"> | |||
<div class="col-sm-12"> | |||
<br /> | |||
<label for="clients"><h4>Clients</h4></label><span class="pull-right"><button type="button" class="btn btn-default" id="create_client">Create Client</button></span> | |||
<div id="clients" style="overflow-y: auto; max-height: 400px;"> | |||
<label for="clients"><h4>Clients</h4></label><span class="pull-right"><button type="button" class="btn btn-default" id="createClient">Create Client</button></span> | |||
<div id="clients"> | |||
<ul class="list-group" id="clientList"> | |||
@if (Model.Clients.Any()) | |||
{ | |||
@@ -75,4 +41,52 @@ | |||
</div> | |||
</div> | |||
<div class="modal fade" id="clientModal" tabindex="-1" role="dialog" aria-labelledby="clientModalLabel" aria-hidden="true"> | |||
<div class="modal-dialog"> | |||
<div class="modal-content"> | |||
<div class="modal-header modal-header-default"> | |||
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span></button> | |||
<h4 class="modal-title" id="clientModalLabel">Client Information</h4> | |||
</div> | |||
<div class="modal-body"> | |||
<input type="hidden" class="form-control" id="clientId" name="clientId" /> | |||
<div class="row"> | |||
<div class="col-sm-12 text-center"> | |||
<div id="clientStatus"> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="form-group"> | |||
<label for="clientName">Application Name <span class="text-danger">*</span></label> | |||
<input type="text" class="form-control" id="clientName" name="clientName" data-val-required="The Client Name field is required." data-val="true" /> | |||
</div> | |||
<div class="form-group"> | |||
<label for="clientCallbackUrl">Homepage URL</label> | |||
<input type="text" class="form-control" id="clientHomepageUrl" name="clientHomepageUrl" /> | |||
</div> | |||
<div class="form-group"> | |||
<label for="clientLogoUrl">Logo URL</label> | |||
<input type="text" class="form-control" id="clientLogoUrl" name="clientLogoUrl" /> | |||
</div> | |||
<p> | |||
<small> | |||
The logo will be rendered at a max size of 100x100. | |||
</small> | |||
</p> | |||
<div class="form-group"> | |||
<label for="clientCallbackUrl">Authorization callback URL <span class="text-danger">*</span></label> | |||
<input type="text" class="form-control" id="clientCallbackUrl" name="clientCallbackUrl" data-val-required="The Authorization callback URL field is required." data-val="true" /> | |||
</div> | |||
<div class="row"> | |||
<div class="col-sm-12"> | |||
<button class="btn btn-primary pull-right hidden clientSubmit" id="clientCreateSubmit" type="submit" name="clientCreateSubmit">Create Client</button> | |||
<button class="btn btn-primary pull-right hidden clientSubmit" id="clientEditSubmit" type="submit" name="clientEditSubmit">Save Client</button> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<bundle src="js/user.settings.developer.min.js" append-version="true"></bundle> |
@@ -15,7 +15,6 @@ | |||
NewPasswordConfirm: password_confirm | |||
}), | |||
success: function (response) { | |||
enableButton('#change_password_submit', 'Change Password'); | |||
if (response.result) { | |||
$("#top_msg").css('display', 'inline', 'important'); | |||
$("#top_msg").html('<div class="alert alert-success alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>Password has successfully been changed.</div>'); | |||
@@ -36,6 +35,8 @@ | |||
}); | |||
$('#delete_account').click(function () { | |||
disableButton('#delete_account', 'Deleting Account...'); | |||
bootbox.confirm("Are you sure you want to delete your account?", function (result) { | |||
if (result) { | |||
$.ajax({ | |||
@@ -55,7 +56,11 @@ | |||
$("#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.responseText) + '</div>'); | |||
} | |||
}); | |||
}).always(function () { | |||
enableButton('#delete_account', 'Delete Account'); | |||
});; | |||
} else { | |||
enableButton('#delete_account', 'Delete Account'); | |||
} | |||
}); | |||
}); |
@@ -13,7 +13,6 @@ $(document).ready(function () { | |||
Description: blog_desc, | |||
}), | |||
success: function (response) { | |||
enableButton('#update_submit', 'Save'); | |||
if (response.result) { | |||
$("#top_msg").css('display', 'inline', 'important'); | |||
$("#top_msg").html('<div class="alert alert-success alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>Settings Saved!</div>'); |
@@ -1,210 +1,131 @@ | |||
$(document).ready(function () { | |||
$('#create_client').click(function () { | |||
var name, redirectUri, logoutUri; | |||
bootbox.prompt("Specify a name for this Client", function (result) { | |||
if (result) { | |||
name = result; | |||
bootbox.prompt("Specify the Redirect URI for this Client", function (result) { | |||
if (result) { | |||
redirectUri = result; | |||
bootbox.prompt("Specify the Logout URI for this Client", function (result) { | |||
if (result) { | |||
logoutUri = result; | |||
$.ajax({ | |||
type: "POST", | |||
url: createClientURL, | |||
data: AddAntiForgeryToken({ name: name, redirectUri: redirectUri, logoutUri: logoutUri }), | |||
success: function (response) { | |||
if (response.result) { | |||
var dialog = bootbox.dialog({ | |||
closeButton: false, | |||
buttons: { | |||
close: { | |||
label: 'Close', | |||
className: 'btn-primary', | |||
callback: function () { | |||
if ($('#noClients')) { | |||
$('#noClients').remove(); | |||
} | |||
var item = $(response.html); | |||
var deleteBtn = item.find('.deleteClient'); | |||
deleteBtn.click(function () { | |||
var clientId = $(this).attr("data-clientId"); | |||
deleteClient(clientId); | |||
}); | |||
$('#clientList').append(item); | |||
} | |||
} | |||
}, | |||
title: "Client Secret", | |||
message: '<label for="clientSecret">Make sure to copy your client secret now.<br />You won\'t be able to see it again!</label><input type="text" class="form-control" id="clientSecret" value="' + response.secret + '">', | |||
}); | |||
dialog.init(function () { | |||
dialog.find('#authToken').click(function () { | |||
$(this).select(); | |||
}); | |||
}); | |||
} | |||
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>'); | |||
} | |||
} | |||
}); | |||
} | |||
}); | |||
} | |||
}); | |||
} | |||
}); | |||
$('#clientModal').on('shown.bs.modal', function (e) { | |||
$("#clientStatus").css('display', 'none', 'important'); | |||
$("#clientStatus").html(''); | |||
$('#clientName').focus(); | |||
}); | |||
$('#clientModal').on('hide.bs.modal', function (e) { | |||
$("#clientStatus").css('display', 'none', 'important'); | |||
$("#clientStatus").html(''); | |||
$(this).find('#clientCreateSubmit').addClass('hidden'); | |||
$(this).find('#clientEditSubmit').addClass('hidden'); | |||
clearInputs('#clientModal'); | |||
}); | |||
$('#clientModal').find('#clientCreateSubmit').click(createClient); | |||
$('#clientModal').find('#clientEditSubmit').click(editClientSave); | |||
$("#createClient").click(function () { | |||
$('#clientModal').find('#clientCreateSubmit').removeClass('hidden'); | |||
$('#clientModal').find('#clientCreateSubmit').text('Create Client'); | |||
$('#clientModal').modal('show'); | |||
}); | |||
$(".editClient").click(function () { | |||
var clientId = $(this).attr("data-clientId"); | |||
editClient(clientId); | |||
}); | |||
$(".deleteClient").click(function () { | |||
var clientId = $(this).attr("data-clientId"); | |||
deleteClient(clientId); | |||
}); | |||
}); | |||
$('#create_authToken').click(function () { | |||
bootbox.prompt("Specify a name for this Auth Token", function (result) { | |||
if (result) { | |||
$.ajax({ | |||
type: "POST", | |||
url: generateTokenURL, | |||
data: AddAntiForgeryToken({ name: result }), | |||
success: function (response) { | |||
if (response.result) { | |||
var dialog = bootbox.dialog({ | |||
closeButton: false, | |||
buttons: { | |||
close: { | |||
label: 'Close', | |||
className: 'btn-primary', | |||
callback: function () { | |||
if ($('#noAuthTokens')) { | |||
$('#noAuthTokens').remove(); | |||
} | |||
var item = $(response.result.html); | |||
var deleteBtn = item.find('.deleteAuthToken'); | |||
var editBtn = item.find('.editAuthToken'); | |||
deleteBtn.click(function () { | |||
var authTokenId = $(this).attr("data-authid"); | |||
deleteAuthToken(authTokenId); | |||
}); | |||
editBtn.click(function () { | |||
var authTokenId = $(this).attr("data-authid"); | |||
editAuthToken(authTokenId); | |||
}); | |||
$('#authTokenList').append(item); | |||
} | |||
} | |||
}, | |||
title: "Authentication Token", | |||
message: '<label for="authToken">Make sure to copy your new personal access token now.<br />You won\'t be able to see it again!</label><input type="text" class="form-control" id="authToken" value="' + response.result.token + '">', | |||
}); | |||
dialog.init(function () { | |||
dialog.find('#authToken').click(function () { | |||
$(this).select(); | |||
}); | |||
}); | |||
} | |||
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>'); | |||
function createClient() { | |||
saveClientInfo(createClientURL, 'Create Client', 'Creating Client...', function (response) { | |||
$('#clientModal').modal('hide'); | |||
var dialog = bootbox.dialog({ | |||
closeButton: false, | |||
buttons: { | |||
close: { | |||
label: 'Close', | |||
className: 'btn-primary', | |||
callback: function () { | |||
if ($('#noClients')) { | |||
$('#noClients').remove(); | |||
} | |||
var item = $(html); | |||
processClientItem(item); | |||
$('#clientList').append(item); | |||
} | |||
}); | |||
} | |||
} | |||
}, | |||
title: "Client Secret", | |||
message: '<label for="clientSecret">Make sure to copy your client secret now.<br />You won\'t be able to see it again!</label><input type="text" class="form-control" id="clientSecret" value="' + response.secret + '">', | |||
}); | |||
}); | |||
$('#revoke_all_authTokens').click(function () { | |||
bootbox.confirm("Are you sure you want to revoke all your clients?<br /><br />This is <b>irreversable</b> and all applications using these clients will stop working.", function (result) { | |||
if (result) { | |||
$.ajax({ | |||
type: "POST", | |||
url: revokeAllClientsURL, | |||
data: AddAntiForgeryToken({}), | |||
success: function (response) { | |||
if (response.result) { | |||
$('#authTokenList').html('<li class="list-group-item text-center" id="noAuthTokens">No Authentication Tokens</li>'); | |||
} | |||
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>'); | |||
} | |||
} | |||
}); | |||
} | |||
dialog.init(function () { | |||
dialog.find('#clientSecret').click(function () { | |||
$(this).select(); | |||
}); | |||
}); | |||
}); | |||
} | |||
$(".deleteAuthToken").click(function () { | |||
var authTokenId = $(this).attr("data-authid"); | |||
deleteAuthToken(authTokenId); | |||
function processClientItem(item) { | |||
item.find('.editClient').click(function () { | |||
var clientId = $(this).attr("data-clientId"); | |||
editClient(clientId); | |||
}); | |||
$(".editAuthToken").click(function () { | |||
var authTokenId = $(this).attr("data-authid"); | |||
editAuthToken(authTokenId); | |||
item.find('.deleteClient').click(function () { | |||
var clientId = $(this).attr("data-clientId"); | |||
deleteClient(clientId); | |||
}); | |||
}); | |||
} | |||
function editAuthToken(authTokenId) { | |||
bootbox.prompt("Specify a new name for this Auth Token", function (result) { | |||
if (result) { | |||
$.ajax({ | |||
type: "POST", | |||
url: editTokenNameURL, | |||
data: AddAntiForgeryToken({ tokenId: authTokenId, name: result }), | |||
success: function (response) { | |||
if (response.result) { | |||
$('#authTokenName_' + authTokenId).html(response.result.name); | |||
} | |||
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>'); | |||
} | |||
} | |||
}); | |||
function editClient(clientId) { | |||
disableButton('.editClient[data-clientId="' + clientId + '"]', 'Loading...'); | |||
$.ajax({ | |||
type: "POST", | |||
url: getClientURL, | |||
data: AddAntiForgeryToken({ clientId: clientId }), | |||
success: function (data) { | |||
if (data.result) { | |||
$('#clientModal').find('#clientId').val(data.client.id); | |||
$('#clientModal').find('#clientName').val(data.client.name); | |||
$('#clientModal').find('#clientHomepageUrl').val(data.client.homepageUrl); | |||
$('#clientModal').find('#clientLogoUrl').val(data.client.logoUrl); | |||
$('#clientModal').find('#clientCallbackUrl').val(data.client.callbackUrl); | |||
$('#clientModal').find('#clientEditSubmit').removeClass('hidden'); | |||
$('#clientModal').find('#clientEditSubmit').text('Save Client'); | |||
$('#clientModal').modal('show'); | |||
enableButton('.editClient[data-clientId="' + clientId + '"]', 'Edit'); | |||
} | |||
} | |||
}); | |||
} | |||
function deleteAuthToken(authTokenId) { | |||
bootbox.confirm("Are you sure you want to revoke this auth token?<br /><br />This is <b>irreversable</b> and all applications using this token will stop working.", function (result) { | |||
if (result) { | |||
$.ajax({ | |||
type: "POST", | |||
url: deleteTokenURL, | |||
data: AddAntiForgeryToken({ tokenId: authTokenId }), | |||
success: function (response) { | |||
if (response.result) { | |||
$('#authToken_' + authTokenId).remove(); | |||
if ($('#authTokenList li').length <= 0) { | |||
$('#authTokenList').html('<li class="list-group-item text-center" id="noAuthTokens">No Authentication Tokens</li>'); | |||
} | |||
} | |||
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>'); | |||
} | |||
} | |||
}); | |||
} | |||
function editClientSave() { | |||
saveClientInfo(editClientURL, 'Save Client', 'Saving Client...', function (response) { | |||
$('#client_' + response.clientId).replaceWith(response.html); | |||
processClientItem($('#client_' + response.clientId)); | |||
$('#clientModal').modal('hide'); | |||
$("#top_msg").css('display', 'inline', 'important'); | |||
$("#top_msg").html('<div class="alert alert-success alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>Successfully Saved Client</div>'); | |||
}); | |||
} | |||
function deleteClient(clientId) { | |||
disableButton('.deleteClient[data-clientId="' + clientId + '"]', 'Deleting...'); | |||
bootbox.confirm("<h2>Are you sure you want to delete this client?</h2><br /><br />This is <b>irreversable</b> and all applications using these client credentials will stop working.", function (result) { | |||
if (result) { | |||
$.ajax({ | |||
@@ -223,7 +144,45 @@ function deleteClient(clientId) { | |||
$("#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>'); | |||
} | |||
} | |||
}).always(function () { | |||
enableButton('.deleteClient[data-clientId="' + clientId + '"]', 'Delete'); | |||
}); | |||
} else { | |||
enableButton('.deleteClient[data-clientId="' + clientId + '"]', 'Delete'); | |||
} | |||
}); | |||
} | |||
function saveClientInfo(url, submitText, submitActionText, callback) { | |||
var name, homepageUrl, logoUrl, callbackUrl; | |||
disableButton('.clientSubmit', submitActionText); | |||
clientId = $('#clientModal').find('#clientId').val(); | |||
name = $('#clientModal').find('#clientName').val(); | |||
homepageUrl = $('#clientModal').find('#clientHomepageUrl').val(); | |||
logoUrl = $('#clientModal').find('#clientLogoUrl').val(); | |||
callbackUrl = $('#clientModal').find('#clientCallbackUrl').val(); | |||
$.ajax({ | |||
type: "POST", | |||
url: url, | |||
data: AddAntiForgeryToken({ clientId: clientId, name: name, homepageUrl: homepageUrl, logoUrl: logoUrl, callbackUrl: callbackUrl }), | |||
success: function (response) { | |||
if (response.result) { | |||
if (callback) { | |||
callback(response); | |||
} | |||
} | |||
else { | |||
$("#clientStatus").css('display', 'inline', 'important'); | |||
$("#clientStatus").html('<div class="alert alert-danger alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>' + parseErrorMessage(response) + '</div>'); | |||
} | |||
}, | |||
error: function (response) { | |||
$("#clientStatus").css('display', 'inline', 'important'); | |||
$("#clientStatus").html('<div class="alert alert-danger alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>' + parseErrorMessage(response.responseText) + '</div>'); | |||
} | |||
}).always(function () { | |||
enableButton('.clientSubmit', submitText); | |||
}); | |||
} |
@@ -135,6 +135,15 @@ function removeAmp(code) { | |||
return code; | |||
} | |||
function clearInputs(parent) { | |||
$(parent).find('input:text').each(function () { | |||
$(this).val(''); | |||
}); | |||
$(parent).find('textarea').each(function () { | |||
$(this).val(''); | |||
}); | |||
} | |||
String.prototype.hashCode = function () { | |||
var hash = 0, i, chr, len; | |||
if (this.length === 0) return hash; |
@@ -21,7 +21,7 @@ | |||
<span class="icon-bar"></span> | |||
<span class="icon-bar"></span> | |||
</button> | |||
<a class="navbar-brand" href="@Url.SubRouteUrl("www", "Home.Index")"><img src="@logoPath" height="20" alt="Teknik"></a> | |||
<a class="navbar-brand" href="@Url.SubRouteUrl("www", "Home.Index")"><img src="@logoPath" height="20" alt="Teknik" /></a> | |||
</div> | |||
<div class="navbar-collapse collapse"> | |||
<ul class="nav navbar-nav"> |