Browse Source

V2 identity sample.

pull/551/head
Sebastian 5 years ago
parent
commit
c9e7a30e59
  1. 28
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/ReferencesFieldBuilder.cs
  2. 9
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/SchemaBuilder.cs
  3. 7
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/StringFieldBuilder.cs
  4. 9
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/TagsFieldBuilder.cs
  5. 6
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateIdentityCommandMiddleware.cs
  6. 333
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateIdentityV2CommandMiddleware.cs
  7. 3
      backend/src/Squidex/Config/Domain/CommandsServices.cs
  8. 1
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Templates/TemplatesTests.cs
  9. 33
      frontend/app/features/apps/pages/apps-page.component.html

28
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/ReferencesFieldBuilder.cs

@ -0,0 +1,28 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{
public class ReferencesFieldBuilder : FieldBuilder
{
public ReferencesFieldBuilder(UpsertSchemaField field, UpsertCommand schema)
: base(field, schema)
{
}
public ReferencesFieldBuilder WithSchemaId(Guid id)
{
Properties<ReferencesFieldProperties>().SchemaId = id;
return this;
}
}
}

9
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/SchemaBuilder.cs

@ -106,6 +106,15 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
return this;
}
public SchemaBuilder AddReferences(string name, Action<ReferencesFieldBuilder> configure)
{
var field = AddField<ReferencesFieldProperties>(name);
configure(new ReferencesFieldBuilder(field, command));
return this;
}
public SchemaBuilder AddString(string name, Action<StringFieldBuilder> configure)
{
var field = AddField<StringFieldProperties>(name);

7
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/StringFieldBuilder.cs

@ -40,6 +40,13 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
return this;
}
public StringFieldBuilder Unique()
{
Properties<StringFieldProperties>().IsUnique = true;
return this;
}
public StringFieldBuilder Pattern(string pattern, string? message = null)
{
Properties<StringFieldProperties>().Pattern = pattern;

9
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/TagsFieldBuilder.cs

@ -5,6 +5,8 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.ObjectModel;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
@ -15,5 +17,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
: base(field, schema)
{
}
public TagsFieldBuilder WithAllowedValues(params string[] values)
{
Properties<TagsFieldProperties>().AllowedValues = new ReadOnlyCollection<string>(values);
return this;
}
}
}

6
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateIdentityCommandMiddleware.cs

@ -51,7 +51,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
return string.Equals(createApp.Template, TemplateName, StringComparison.OrdinalIgnoreCase);
}
private static async Task<NamedId<Guid>> CreateAuthenticationSchemeSchemaAsync(Func<ICommand, Task> publish)
private static Task CreateAuthenticationSchemeSchemaAsync(Func<ICommand, Task> publish)
{
var schema =
SchemaBuilder.Create("Authentication Schemes")
@ -71,9 +71,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
.Hints("Additional scopes you want from the provider."))
.Build();
await publish(schema);
return NamedId.Of(schema.SchemaId, schema.Name);
return publish(schema);
}
private static Task CreateClientsSchemaAsync(Func<ICommand, Task> publish)

333
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateIdentityV2CommandMiddleware.cs

@ -0,0 +1,333 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Fluid;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Templates.Builders;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Apps.Templates
{
public sealed class CreateIdentityV2CommandMiddleware : ICommandMiddleware
{
private const string TemplateName = "IdentityV2";
public async Task HandleAsync(CommandContext context, NextDelegate next)
{
if (context.IsCompleted && context.Command is CreateApp createApp && IsRightTemplate(createApp))
{
var appId = NamedId.Of(createApp.AppId, createApp.Name);
var publish = new Func<ICommand, Task<CommandContext>>(command =>
{
if (command is IAppCommand appCommand)
{
appCommand.AppId = appId;
}
return context.CommandBus.PublishAsync(command);
});
var apiScopeId = await CreateApiScopesSchemaAsync(publish);
await Task.WhenAll(
CreateApiResourcesSchemaAsync(publish, apiScopeId),
CreateAuthenticationSchemeSchemaAsync(publish),
CreateClientsSchemaAsync(publish),
CreateIdentityResourcesSchemaAsync(publish),
CreateSettingsSchemaAsync(publish),
CreateUsersSchemaAsync(publish));
}
await next(context);
}
private static bool IsRightTemplate(CreateApp createApp)
{
return string.Equals(createApp.Template, TemplateName, StringComparison.OrdinalIgnoreCase);
}
private static Task CreateAuthenticationSchemeSchemaAsync(Func<ICommand, Task> publish)
{
var schema =
SchemaBuilder.Create("Authentication Schemes")
.AddString("Provider", f => f
.AsDropDown("Facebook", "Google", "Microsoft", "Twitter")
.Unique()
.Required()
.ShowInList()
.Hints("The name and type of the provider."))
.AddString("Client Id", f => f
.Required()
.ShowInList()
.Hints("The client id that you must configure at the external provider."))
.AddString("Client Secret", f => f
.Required()
.Hints("The client secret that you must configure at the external provider."))
.AddTags("Scopes", f => f
.Hints("Additional scopes you want from the provider."))
.Build();
return publish(schema);
}
private static Task CreateClientsSchemaAsync(Func<ICommand, Task> publish)
{
var schema =
SchemaBuilder.Create("Clients")
.AddString("Client Id", f => f
.Unique()
.Required()
.Hints("Unique id of the client."))
.AddString("Client Name", f => f
.Localizable()
.Hints("Client display name (used for logging and consent screen)."))
.AddString("Client Uri", f => f
.Localizable()
.Hints("URI to further information about client (used on consent screen)."))
.AddAssets("Logo", f => f
.MustBeImage()
.Hints("URI to client logo (used on consent screen)."))
.AddBoolean("Require Consent", f => f
.AsToggle()
.Hints("Specifies whether a consent screen is required."))
.AddBoolean("Disabled", f => f
.AsToggle()
.Hints("Enable or disable the client."))
.AddBoolean("Allow Offline Access", f => f
.AsToggle()
.Hints("Gets or sets a value indicating whether to allow offline access."))
.AddTags("Allowed Grant Types", f => f
.WithAllowedValues("implicit", "hybrid", "authorization_code", "client_credentials")
.Hints("Specifies the allowed grant types."))
.AddTags("Client Secrets", f => f
.Hints("Client secrets - only relevant for flows that require a secret."))
.AddTags("Allowed Scopes", f => f
.Hints("Specifies the api scopes that the client is allowed to request."))
.AddTags("Redirect Uris", f => f
.Hints("Specifies allowed URIs to return tokens or authorization codes to"))
.AddTags("Post Logout Redirect Uris", f => f
.Hints("Specifies allowed URIs to redirect to after logout."))
.AddTags("Allowed Cors Origins", f => f
.Hints("Gets or sets the allowed CORS origins for JavaScript clients."))
.Build();
return publish(schema);
}
private static Task CreateSettingsSchemaAsync(Func<ICommand, Task> publish)
{
var schema =
SchemaBuilder.Create("Settings").Singleton()
.AddString("Site Name", f => f
.Localizable()
.Hints("The name of your website."))
.AddAssets("Logo", f => f
.MustBeImage()
.Hints("Logo that is rendered in the header."))
.AddString("Footer Text", f => f
.Localizable()
.Hints("The optional footer text."))
.AddString("PrivacyPolicyUrl", f => f
.Localizable()
.Hints("The link to your privacy policies."))
.AddString("LegalUrl", f => f
.Localizable()
.Hints("The link to your legal information."))
.AddString("Email Confirmation Text", f => f
.AsTextArea()
.Localizable()
.Hints("The text for the confirmation email."))
.AddString("Email Confirmation Subject", f => f
.AsTextArea()
.Localizable()
.Hints("The subject for the confirmation email."))
.AddString("Email Password Reset Text", f => f
.AsTextArea()
.Localizable()
.Hints("The text for the password reset email."))
.AddString("Email Password Reset Subject", f => f
.AsTextArea()
.Localizable()
.Hints("The subject for the password reset email."))
.AddString("Terms of Service Url", f => f
.Localizable()
.Hints("The link to your tems of service."))
.AddString("Bootstrap Url", f => f
.Hints("The link to a custom bootstrap theme."))
.AddString("Styles Url", f => f
.Hints("The link to a stylesheet."))
.AddString("SMTP From", f => f
.Hints("The SMTP sender address."))
.AddString("SMTP Server", f => f
.Hints("The smpt server."))
.AddString("SMTP Username", f => f
.Hints("The username for your SMTP server."))
.AddString("SMTP Password", f => f
.Hints("The password for your SMTP server."))
.AddString("Google Analytics Id", f => f
.Hints("The id to your google analytics account."))
.Build();
return publish(schema);
}
private static async Task CreateUsersSchemaAsync(Func<ICommand, Task> publish)
{
var schema =
SchemaBuilder.Create("Users")
.AddString("Username", f => f
.Required()
.ShowInList()
.Hints("The unique username to login."))
.AddString("Email", f => f
.Pattern(@"^[a-zA-Z0-9.!#$%&’*+\\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:.[a-zA-Z0-9-]+)*$", "Must be an email address.")
.Required()
.ShowInList()
.Hints("The unique email to login."))
.AddString("Phone Number", f => f
.Hints("Phone number of the user."))
.AddTags("Roles", f => f
.Hints("The roles of the user."))
.AddJson("Claims", f => f
.Hints("The claims of the user."))
.AddBoolean("Email Confirmed", f => f
.AsToggle()
.Hints("Indicates if the email is confirmed."))
.AddBoolean("Phone Number Confirmed", f => f
.AsToggle()
.Hints("Indicates if the phone number is confirmed."))
.AddBoolean("LockoutEnabled", f => f
.AsToggle()
.Hints("Toggle on to lock out the user."))
.AddDateTime("Lockout End Date Utc", f => f
.AsDateTime()
.Disabled()
.Hints("Indicates when the lockout ends."))
.AddTags("Login Keys", f => f
.Disabled()
.Hints("Login information for querying."))
.AddJson("Logins", f => f
.Disabled()
.Hints("Login information."))
.AddJson("Tokens", f => f
.Disabled()
.Hints("Login tokens."))
.AddNumber("Access Failed Count", f => f
.Disabled()
.Hints("The number of failed login attempts."))
.AddString("Password Hash", f => f
.Disabled()
.Hints("The hashed password."))
.AddString("Normalized Email", f => f
.Disabled()
.Hints("The normalized email for querying."))
.AddString("Normalized Username", f => f
.Disabled()
.Hints("The normalized user name for querying."))
.AddString("Security Stamp", f => f
.Disabled()
.Hints("Internal security stamp"))
.WithScripts(DefaultScripts.GenerateUsername)
.Build();
await publish(schema);
}
private static async Task<NamedId<Guid>> CreateApiScopesSchemaAsync(Func<ICommand, Task> publish)
{
var schema =
SchemaBuilder.Create("API Scopes")
.AddString("Name", f => f
.Unique()
.Required()
.ShowInList()
.Hints("The unique name of the API scope."))
.AddString("Display Name", f => f
.Localizable()
.Hints("The display name of the API scope."))
.AddString("Description", f => f
.Localizable()
.Hints("The description name of the API scope."))
.AddBoolean("Disabled", f => f
.AsToggle()
.Hints("Enable or disable the scope."))
.AddBoolean("Emphasize", f => f
.AsToggle()
.Hints("Emphasize the API scope for important scopes."))
.AddTags("User Claims", f => f
.Hints("List of accociated user claims that should be included when this resource is requested."))
.Build();
await publish(schema);
return NamedId.Of(schema.SchemaId, schema.Name);
}
private static Task CreateApiResourcesSchemaAsync(Func<ICommand, Task> publish, NamedId<Guid> scopeId)
{
var schema =
SchemaBuilder.Create("API Resources")
.AddString("Name", f => f
.Unique()
.Required()
.ShowInList()
.Hints("The unique name of the API."))
.AddString("Display Name", f => f
.Localizable()
.Hints("The display name of the API."))
.AddString("Description", f => f
.Localizable()
.Hints("The description name of the API."))
.AddBoolean("Disabled", f => f
.AsToggle()
.Hints("Enable or disable the API."))
.AddReferences("Scopes", f => f
.WithSchemaId(scopeId.Id)
.Hints("The scopes for this API."))
.AddTags("User Claims", f => f
.Hints("List of accociated user claims that should be included when this resource is requested."))
.Build();
return publish(schema);
}
private static Task CreateIdentityResourcesSchemaAsync(Func<ICommand, Task> publish)
{
var schema =
SchemaBuilder.Create("Identity Resources")
.AddString("Name", f => f
.Unique()
.Required()
.ShowInList()
.Hints("The unique name of the identity information."))
.AddString("Display Name", f => f
.Localizable()
.Hints("The display name of the identity information."))
.AddString("Description", f => f
.Localizable()
.Hints("The description name of the identity information."))
.AddBoolean("Required", f => f
.AsToggle()
.Hints("Specifies whether the user can de-select the scope on the consent screen."))
.AddBoolean("Disabled", f => f
.AsToggle()
.Hints("Enable or disable the scope."))
.AddBoolean("Emphasize", f => f
.AsToggle()
.Hints("Emphasize the API scope for important scopes."))
.AddTags("User Claims", f => f
.Hints("List of accociated user claims that should be included when this resource is requested."))
.Build();
return publish(schema);
}
}
}

3
backend/src/Squidex/Config/Domain/CommandsServices.cs

@ -107,6 +107,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<CreateIdentityCommandMiddleware>()
.As<ICommandMiddleware>();
services.AddSingletonAs<CreateIdentityV2CommandMiddleware>()
.As<ICommandMiddleware>();
services.AddSingletonAs<CreateProfileCommandMiddleware>()
.As<ICommandMiddleware>();

1
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Templates/TemplatesTests.cs

@ -24,6 +24,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
{
new object[] { new CreateBlogCommandMiddleware(), "blog" },
new object[] { new CreateIdentityCommandMiddleware(), "identity" },
new object[] { new CreateIdentityV2CommandMiddleware(), "identityV2" },
new object[] { new CreateProfileCommandMiddleware(), "profile" }
};

33
frontend/app/features/apps/pages/apps-page.component.html

@ -76,39 +76,56 @@
</div>
</div>
<div class="card card-template card-href" (click)="createNewApp('Profile')">
<div class="card card-template card-href" (click)="createNewApp('Identity')">
<div class="card-body">
<div class="card-image">
<img src="./images/add-profile.svg" />
<img src="./images/add-identity.svg" />
</div>
<h3 class="card-title">New Profile Sample</h3>
<h3 class="card-title">New Identity App</h3>
<div class="card-text">
<div>Create your profile page.</div>
<div>Create app for Squidex Identity.</div>
<div>
Sample Code at <a href="https://github.com/Squidex/squidex-samples" sqxStopClick sqxExternalLink>Github</a>
<a href="https://github.com/Squidex/squidex-identity" sqxStopClick sqxExternalLink>Project</a>
</div>
</div>
</div>
</div>
<div class="card card-template card-href" (click)="createNewApp('Identity')">
<div class="card card-template card-href" (click)="createNewApp('IdentityV2')">
<div class="card-body">
<div class="card-image">
<img src="./images/add-identity.svg" />
</div>
<h3 class="card-title">New Identity App</h3>
<h3 class="card-title">New Identity App V2</h3>
<div class="card-text">
<div>Create app for Squidex Identity.</div>
<div>Create app for Squidex Identity V2.</div>
<div>
<a href="https://github.com/Squidex/squidex-identity" sqxStopClick sqxExternalLink>Project</a>
</div>
</div>
</div>
</div>
<div class="card card-template card-href" (click)="createNewApp('Profile')">
<div class="card-body">
<div class="card-image">
<img src="./images/add-profile.svg" />
</div>
<h3 class="card-title">New Profile Sample</h3>
<div class="card-text">
<div>Create your profile page.</div>
<div>
Sample Code at <a href="https://github.com/Squidex/squidex-samples" sqxStopClick sqxExternalLink>Github</a>
</div>
</div>
</div>
</div>
</div>
<div *ngIf="info" class="apps-section">

Loading…
Cancel
Save