Skip to content

Commit 2236874

Browse files
User info (#1278)
* Menu * Move to components. * Show a marker for unsaved changes. * User info field. * User info implementation. * Fix tests * Update tests * Update auth. * Fix DB provider. * User info * Instructions.
1 parent 476bb8f commit 2236874

File tree

140 files changed

+31412
-292
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

140 files changed

+31412
-292
lines changed

backend/extensions/Squidex.Extensions/Text/Azure/AzureIndexDefinition.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,14 @@ public static SearchIndex Create(string indexName)
116116
{
117117
IsFilterable = true,
118118
},
119+
new SimpleField("userInfoApiKey", SearchFieldDataType.String)
120+
{
121+
IsFilterable = true,
122+
},
123+
new SimpleField("userInfoField", SearchFieldDataType.String)
124+
{
125+
IsFilterable = true,
126+
},
119127
};
120128

121129
foreach (var (field, analyzer) in FieldAnalyzers.Values)

backend/extensions/Squidex.Extensions/Text/Azure/AzureTextIndex.cs

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,20 +123,52 @@ private Task SearchBySchemaAsync(List<(DomainId, double)> result, string text, I
123123
CancellationToken ct = default)
124124
{
125125
var searchField = GetServeField(scope);
126+
var searchFilter = $"{string.Join(" or ", schemaIds.Select(x => $"schemaId eq '{x}'"))} and {searchField} eq true";
126127

127-
var filter = $"{string.Join(" or ", schemaIds.Select(x => $"schemaId eq '{x}'"))} and {searchField} eq true";
128-
129-
return SearchAsync(result, text, filter, take, factor, ct);
128+
return SearchAsync(result, text, searchFilter, take, factor, ct);
130129
}
131130

132131
private Task SearchByAppAsync(List<(DomainId, double)> result, string text, App app, SearchScope scope, int take, double factor,
133132
CancellationToken ct = default)
134133
{
135134
var searchField = GetServeField(scope);
135+
var searchFilter = $"appId eq '{app.Id}' and {searchField} eq true";
136+
137+
return SearchAsync(result, text, searchFilter, take, factor, ct);
138+
}
139+
140+
public async Task<UserInfoResult?> FindUserInfo(App app, ApiKeyQuery query, SearchScope scope,
141+
CancellationToken ct = default)
142+
{
143+
Guard.NotNull(app);
144+
Guard.NotNull(query);
145+
146+
var searchField = GetServeField(scope);
147+
var searchFilter = $"appId eq '{app.Id}' and {searchField} eq true and userApiKey eq '{query.ApiKey}'";
148+
var searchOptions = new SearchOptions
149+
{
150+
Filter = searchFilter,
151+
};
136152

137-
var filter = $"appId eq '{app.Id}' and {searchField} eq true";
153+
searchOptions.Select.Add("contentId");
154+
searchOptions.Select.Add("userInfoApiKey");
155+
searchOptions.Select.Add("userInfoRole");
156+
searchOptions.Size = 1;
157+
searchOptions.QueryType = SearchQueryType.Full;
158+
159+
var results = await searchClient.SearchAsync<SearchDocument>(searchFilter, searchOptions, ct);
160+
161+
await foreach (var item in results.Value.GetResultsAsync().WithCancellation(ct))
162+
{
163+
if (item != null)
164+
{
165+
var id = DomainId.Create(item.Document["contentId"].ToString()!);
166+
167+
return new UserInfoResult(id, item.Document["userInfoRole"].ToString()!);
168+
}
169+
}
138170

139-
return SearchAsync(result, text, filter, take, factor, ct);
171+
return null;
140172
}
141173

142174
private async Task SearchAsync(List<(DomainId, double)> result, string text, string filter, int take, double factor,

backend/extensions/Squidex.Extensions/Text/Azure/CommandFactory.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,28 @@ private static void UpsertTextEntry(UpsertIndexEntry upsert, IList<IndexDocument
7070
}
7171
}
7272

73+
if (upsert.UserInfos != null)
74+
{
75+
foreach (var userInfo in upsert.UserInfos)
76+
{
77+
var geoDocument = new SearchDocument
78+
{
79+
["docId"] = upsert.ToDocId(),
80+
["appId"] = upsert.UniqueContentId.AppId.ToString(),
81+
["appName"] = string.Empty,
82+
["contentId"] = upsert.UniqueContentId.ContentId.ToString(),
83+
["schemaId"] = upsert.SchemaId.Id.ToString(),
84+
["schemaName"] = upsert.SchemaId.Name,
85+
["serveAll"] = upsert.ServeAll,
86+
["servePublished"] = upsert.ServePublished,
87+
["userInfoApiKey"] = userInfo,
88+
["userInfoRole"] = userInfo.Role,
89+
};
90+
91+
batch.Add(IndexDocumentsAction.MergeOrUpload(geoDocument));
92+
}
93+
}
94+
7395
if (upsert.Texts is { Count: > 0 })
7496
{
7597
var document = new SearchDocument

backend/extensions/Squidex.Extensions/Text/ElasticSearch/CommandFactory.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,28 @@ void AddArgs(object arg)
8181
}
8282
}
8383

84+
if (upsert.UserInfos != null)
85+
{
86+
foreach (var userInfo in upsert.UserInfos)
87+
{
88+
var userInfoApiKey = userInfo.ApiKey;
89+
var userInfoRole = userInfo.Role;
90+
91+
AddArgs(new
92+
{
93+
appId = upsert.UniqueContentId.AppId.ToString(),
94+
appName = string.Empty,
95+
contentId = upsert.UniqueContentId.ContentId.ToString(),
96+
schemaId = upsert.SchemaId.Id.ToString(),
97+
schemaName = upsert.SchemaId.Name,
98+
serveAll = upsert.ServeAll,
99+
servePublished = upsert.ServePublished,
100+
userInfoApiKey,
101+
userInfoRole,
102+
});
103+
}
104+
}
105+
84106
if (upsert.Texts is { Count: > 0 })
85107
{
86108
var texts = new Dictionary<string, string>();

backend/extensions/Squidex.Extensions/Text/ElasticSearch/ElasticSearchIndexDefinition.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ public static Task ApplyAsync(IElasticSearchClient client, string indexName,
105105
{
106106
type = "geo_point",
107107
},
108+
["userInfoApiKey"] = new
109+
{
110+
type = "text",
111+
},
108112
},
109113
};
110114

backend/extensions/Squidex.Extensions/Text/ElasticSearch/ElasticSearchTextIndex.cs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515

1616
namespace Squidex.Extensions.Text.ElasticSearch;
1717

18-
public sealed partial class ElasticSearchTextIndex(IElasticSearchClient elasticClient, string indexName, IJsonSerializer jsonSerializer) : ITextIndex, IInitializable
18+
public sealed partial class ElasticSearchTextIndex(IElasticSearchClient elasticClient, string indexName, IJsonSerializer jsonSerializer)
19+
: ITextIndex, IInitializable
1920
{
2021
private static readonly Regex RegexLanguageNormal = BuildLanguageRegexNormal();
2122
private static readonly Regex RegexLanguageStart = BuildLanguageRegexStart();
@@ -198,13 +199,62 @@ public Task ExecuteAsync(IndexCommand[] commands,
198199
return await SearchAsync(elasticQuery, ct);
199200
}
200201

202+
public async Task<UserInfoResult?> FindUserInfo(App app, ApiKeyQuery query, SearchScope scope,
203+
CancellationToken ct = default)
204+
{
205+
Guard.NotNull(app);
206+
Guard.NotNull(query);
207+
208+
var serveField = GetServeField(scope);
209+
210+
var elasticQuery = new
211+
{
212+
query = new
213+
{
214+
@bool = new
215+
{
216+
filter = new object[]
217+
{
218+
new
219+
{
220+
term = new Dictionary<string, string>
221+
{
222+
[serveField] = "true",
223+
},
224+
},
225+
new
226+
{
227+
term = new Dictionary<string, string>
228+
{
229+
["userInfoApiKey.keyword"] = query.ApiKey,
230+
},
231+
},
232+
},
233+
},
234+
},
235+
_source = new[]
236+
{
237+
"contentId",
238+
"userInfoApiKey",
239+
"userInfoRole",
240+
},
241+
size = 1,
242+
};
243+
244+
var hits = await elasticClient.SearchAsync(indexName, elasticQuery, ct);
245+
var hit = hits.FirstOrDefault();
246+
247+
return hit != null ?
248+
new UserInfoResult(DomainId.Create(hit["_source"]["contentId"]), hit["_source"]["userInfoRole"]) :
249+
null;
250+
}
251+
201252
private async Task<List<DomainId>> SearchAsync(object query,
202253
CancellationToken ct)
203254
{
204255
var hits = await elasticClient.SearchAsync(indexName, query, ct);
205256

206257
var ids = new List<DomainId>();
207-
208258
foreach (var item in hits)
209259
{
210260
ids.Add(DomainId.Create(item["_source"]["contentId"]));

backend/i18n/frontend_de.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@
198198
"common.administration": "Verwaltung",
199199
"common.administrationPageTitle": "Verwaltung",
200200
"common.api": "API",
201+
"common.apiKey": "API Key",
201202
"common.apps": "Apps",
202203
"common.aspectRatio": "Seitenverhältnis",
203204
"common.assets": "Assets",
@@ -523,6 +524,7 @@
523524
"contents.unsetValueConfirmTitle": "Möchten Sie den Wert zurücksetzen?",
524525
"contents.updated": "Inhalt erfolgreich aktualisiert.",
525526
"contents.updateFailed": "Fehler beim Aktualisieren des Inhalts. Bitte neu laden.",
527+
"contents.userInfo.instructions": "Connect as this user with the following header. The first part of the value is the app name.\n\nDo not miss the colon and remember the assign a restrictive role.",
526528
"contents.validate": "Validieren",
527529
"contents.validationHint": "Bitte denken Sie daran, alle Sprachen zu überprüfen, wenn Sie Validierungsfehler sehen.",
528530
"contents.versionCompare": "Vergleichen",
@@ -984,6 +986,9 @@
984986
"schemas.fieldTypes.tags.countMin": "Min. Elemente",
985987
"schemas.fieldTypes.tags.description": "Spezialformat für Tags.",
986988
"schemas.fieldTypes.ui.description": "Trennzeichen für die Bearbeitungsoberfläche.",
989+
"schemas.fieldTypes.user.description": "User Credentials for custom Auth.",
990+
"schemas.fieldTypes.userInfo.defaultRole": "Default Role",
991+
"schemas.fieldTypes.userInfo.defaultRoleHint": "The default role. If a value is set a user info with a random API Key will be generated.",
987992
"schemas.hideFieldFailed": "Fehler beim Ausblenden des Feldes. Bitte neu laden.",
988993
"schemas.import": "Schema importieren",
989994
"schemas.indexes.addIndex": "Index hinzufügen",

backend/i18n/frontend_en.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@
198198
"common.administration": "Administration",
199199
"common.administrationPageTitle": "Administration",
200200
"common.api": "API",
201+
"common.apiKey": "API Key",
201202
"common.apps": "Apps",
202203
"common.aspectRatio": "AspectRatio",
203204
"common.assets": "Assets",
@@ -523,6 +524,7 @@
523524
"contents.unsetValueConfirmTitle": "Do you want to unset the value?",
524525
"contents.updated": "Content updated successfully.",
525526
"contents.updateFailed": "Failed to update content. Please reload.",
527+
"contents.userInfo.instructions": "Connect as this user with the following header. The first part of the value is the app name.\n\nDo not miss the colon and remember the assign a restrictive role.",
526528
"contents.validate": "Validate",
527529
"contents.validationHint": "Please remember to check all languages when you see validation errors.",
528530
"contents.versionCompare": "Compare",
@@ -984,6 +986,9 @@
984986
"schemas.fieldTypes.tags.countMin": "Min Items",
985987
"schemas.fieldTypes.tags.description": "Special format for tags.",
986988
"schemas.fieldTypes.ui.description": "Separator for editing UI.",
989+
"schemas.fieldTypes.user.description": "User Credentials for custom Auth.",
990+
"schemas.fieldTypes.userInfo.defaultRole": "Default Role",
991+
"schemas.fieldTypes.userInfo.defaultRoleHint": "The default role. If a value is set a user info with a random API Key will be generated.",
987992
"schemas.hideFieldFailed": "Failed to hide field. Please reload.",
988993
"schemas.import": "Import schema",
989994
"schemas.indexes.addIndex": "Add Index",

backend/i18n/frontend_fr.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@
198198
"common.administration": "Administration",
199199
"common.administrationPageTitle": "Administration",
200200
"common.api": "API",
201+
"common.apiKey": "API Key",
201202
"common.apps": "applications",
202203
"common.aspectRatio": "Ratio d'aspect",
203204
"common.assets": "Actifs",
@@ -523,6 +524,7 @@
523524
"contents.unsetValueConfirmTitle": "Voulez-vous annuler la valeur\u00A0?",
524525
"contents.updated": "Contenu mis à jour avec succès.",
525526
"contents.updateFailed": "Échec de la mise à jour du contenu. Veuillez recharger.",
527+
"contents.userInfo.instructions": "Connect as this user with the following header. The first part of the value is the app name.\n\nDo not miss the colon and remember the assign a restrictive role.",
526528
"contents.validate": "Valider",
527529
"contents.validationHint": "N'oubliez pas de vérifier toutes les langues lorsque vous voyez des erreurs de validation.",
528530
"contents.versionCompare": "Comparer",
@@ -984,6 +986,9 @@
984986
"schemas.fieldTypes.tags.countMin": "Articles minimum",
985987
"schemas.fieldTypes.tags.description": "Format spécial pour les balises.",
986988
"schemas.fieldTypes.ui.description": "Séparateur pour l'édition de l'interface utilisateur.",
989+
"schemas.fieldTypes.user.description": "User Credentials for custom Auth.",
990+
"schemas.fieldTypes.userInfo.defaultRole": "Default Role",
991+
"schemas.fieldTypes.userInfo.defaultRoleHint": "The default role. If a value is set a user info with a random API Key will be generated.",
987992
"schemas.hideFieldFailed": "Impossible de masquer le champ. Veuillez recharger.",
988993
"schemas.import": "Calendrier d'importation",
989994
"schemas.indexes.addIndex": "Add Index",

backend/i18n/frontend_it.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@
198198
"common.administration": "Amministrazione",
199199
"common.administrationPageTitle": "Amministrazione",
200200
"common.api": "API",
201+
"common.apiKey": "API Key",
201202
"common.apps": "App",
202203
"common.aspectRatio": "Proporzioni",
203204
"common.assets": "Risorse",
@@ -523,6 +524,7 @@
523524
"contents.unsetValueConfirmTitle": "Sei sicuro di voler annullare il valore impostato?",
524525
"contents.updated": "Contenuto aggiornato con successo.",
525526
"contents.updateFailed": "Non è stato possibile aggiornare il contenuto. Per favore ricarica.",
527+
"contents.userInfo.instructions": "Connect as this user with the following header. The first part of the value is the app name.\n\nDo not miss the colon and remember the assign a restrictive role.",
526528
"contents.validate": "Convalida",
527529
"contents.validationHint": "Ricorda di verificare tutte le lingue quando vedi errori di validazione.",
528530
"contents.versionCompare": "Confronta",
@@ -984,6 +986,9 @@
984986
"schemas.fieldTypes.tags.countMin": "Numero min di Elementi",
985987
"schemas.fieldTypes.tags.description": "Formato speciale per i tag.",
986988
"schemas.fieldTypes.ui.description": "Separatore per il pannello delle modifiche della UI.",
989+
"schemas.fieldTypes.user.description": "User Credentials for custom Auth.",
990+
"schemas.fieldTypes.userInfo.defaultRole": "Default Role",
991+
"schemas.fieldTypes.userInfo.defaultRoleHint": "The default role. If a value is set a user info with a random API Key will be generated.",
987992
"schemas.hideFieldFailed": "Non è stato possibile nascondere il campo. Per favore ricarica.",
988993
"schemas.import": "Importa uno schema",
989994
"schemas.indexes.addIndex": "Add Index",

0 commit comments

Comments
 (0)