merge newui branch
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
window.app = window.app || {};
|
||||
window.app.oauth2 = window.app.oauth2 || {};
|
||||
|
||||
// note: data is the providerSettingsModal form store
|
||||
window.app.oauth2.apple = function(providerInfo, namePrefix, data) {
|
||||
const uniqueId = "apple_" + app.utils.randomString();
|
||||
|
||||
return t.div(
|
||||
{ pbEvent: "oauth2AppleOptions", className: "oauth2-apple-options" },
|
||||
t.button(
|
||||
{
|
||||
type: "button",
|
||||
className: "btn sm secondary",
|
||||
onclick: () => {
|
||||
app.modals.openAppleSecretGenerator({
|
||||
ongenerate: (secret) => {
|
||||
data.config.clientSecret = secret;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
t.i({ className: "ri-key-line" }),
|
||||
t.span({ className: "txt" }, "Generate secret"),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
window.app.modals = window.app.modals || {};
|
||||
|
||||
window.app.modals.openAppleSecretGenerator = function(modalSettings = {
|
||||
onbeforeopen: null,
|
||||
onafteropen: null,
|
||||
onbeforeclose: null,
|
||||
onafterclose: null,
|
||||
ongenerate: null, // (secret) => {}
|
||||
}) {
|
||||
const modal = appleSecretGeneratorModal(modalSettings);
|
||||
if (!modal) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
app.modals.open(modal);
|
||||
};
|
||||
|
||||
function appleSecretGeneratorModal(modalSettings = {}) {
|
||||
let modal;
|
||||
|
||||
const uniqueId = "secret_generator_" + app.utils.randomString();
|
||||
|
||||
const maxDuration = 15777000; // 6 months
|
||||
|
||||
const data = store({
|
||||
clientId: "",
|
||||
teamId: "",
|
||||
keyId: "",
|
||||
privateKey: "",
|
||||
duration: maxDuration,
|
||||
isSubmitting: false,
|
||||
});
|
||||
|
||||
async function submit() {
|
||||
data.isSubmitting = true;
|
||||
|
||||
try {
|
||||
const result = await app.pb.settings.generateAppleClientSecret(
|
||||
data.clientId,
|
||||
data.teamId,
|
||||
data.keyId,
|
||||
data.privateKey.trim(),
|
||||
data.duration,
|
||||
);
|
||||
|
||||
data.isSubmitting = false;
|
||||
|
||||
app.toasts.success("Successfully generated client secret.");
|
||||
|
||||
modalSettings.ongenerate?.(result.secret);
|
||||
|
||||
app.modals.close(modal);
|
||||
} catch (err) {
|
||||
if (!err.isAbort) {
|
||||
app.checkApiError(err);
|
||||
data.isSubmitting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
modal = t.div(
|
||||
{
|
||||
pbEvent: "appleSecretGeneratorModal",
|
||||
className: "modal popup apple-secret-generator-modal",
|
||||
onbeforeopen: (el) => {
|
||||
return modalSettings.onbeforeopen?.(el);
|
||||
},
|
||||
onafteropen: (el) => {
|
||||
modalSettings.onafteropen?.(el);
|
||||
},
|
||||
onbeforeclose: (el) => {
|
||||
return modalSettings.onbeforeclose?.(el);
|
||||
},
|
||||
onafterclose: (el) => {
|
||||
modalSettings.onafterclose?.(el);
|
||||
el?.remove();
|
||||
},
|
||||
},
|
||||
t.header(
|
||||
{ className: "modal-header" },
|
||||
t.h5({ className: "m-auto" }, "Generate Apple client secret"),
|
||||
),
|
||||
t.form(
|
||||
{
|
||||
id: uniqueId + "_form",
|
||||
className: "modal-content",
|
||||
onsubmit: (e) => {
|
||||
e.preventDefault();
|
||||
submit();
|
||||
},
|
||||
},
|
||||
t.div(
|
||||
{ className: "grid" },
|
||||
t.div(
|
||||
{ className: "col-sm-6" },
|
||||
t.div(
|
||||
{ className: "field" },
|
||||
t.label({ htmlFor: uniqueId + ".clientId" }, "Client ID"),
|
||||
t.input({
|
||||
id: uniqueId + ".clientId",
|
||||
name: "clientId",
|
||||
type: "text",
|
||||
required: true,
|
||||
value: () => data.clientId || "",
|
||||
oninput: (e) => data.clientId = e.target.value,
|
||||
}),
|
||||
),
|
||||
),
|
||||
t.div(
|
||||
{ className: "col-sm-6" },
|
||||
t.div(
|
||||
{ className: "field" },
|
||||
t.label({ htmlFor: uniqueId + ".teamId" }, "Team ID"),
|
||||
t.input({
|
||||
id: uniqueId + ".teamId",
|
||||
name: "teamId",
|
||||
type: "text",
|
||||
required: true,
|
||||
value: () => data.teamId || "",
|
||||
oninput: (e) => data.teamId = e.target.value,
|
||||
}),
|
||||
),
|
||||
),
|
||||
t.div(
|
||||
{ className: "col-sm-6" },
|
||||
t.div(
|
||||
{ className: "field" },
|
||||
t.label({ htmlFor: uniqueId + ".keyId" }, "Key ID"),
|
||||
t.input({
|
||||
id: uniqueId + ".keyId",
|
||||
name: "keyId",
|
||||
type: "text",
|
||||
required: true,
|
||||
value: () => data.keyId || "",
|
||||
oninput: (e) => data.keyId = e.target.value,
|
||||
}),
|
||||
),
|
||||
),
|
||||
t.div(
|
||||
{ className: "col-sm-6" },
|
||||
t.div(
|
||||
{ className: "field" },
|
||||
t.label({ htmlFor: uniqueId + ".duration" }, "Duration (in seconds)"),
|
||||
t.input({
|
||||
id: uniqueId + ".duration",
|
||||
name: "duration",
|
||||
type: "number",
|
||||
min: 0,
|
||||
step: 1,
|
||||
max: maxDuration,
|
||||
required: true,
|
||||
value: () => data.duration || 0,
|
||||
oninput: (e) => data.duration = parseInt(e.target.value, 10),
|
||||
}),
|
||||
),
|
||||
t.div(
|
||||
{ className: "field-help" },
|
||||
`Max ${maxDuration} seconds (~${(maxDuration / (60 * 60 * 24 * 30)) << 0} months).`,
|
||||
),
|
||||
),
|
||||
t.div(
|
||||
{ className: "col-sm-12" },
|
||||
t.div(
|
||||
{ className: "field" },
|
||||
t.label({ htmlFor: uniqueId + ".privateKey" }, "Private key"),
|
||||
t.textarea({
|
||||
id: uniqueId + ".privateKey",
|
||||
name: "privateKey",
|
||||
type: "text",
|
||||
required: true,
|
||||
rows: 8,
|
||||
placeholder: "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----",
|
||||
value: () => data.privateKey || "",
|
||||
oninput: (e) => data.privateKey = e.target.value,
|
||||
}),
|
||||
),
|
||||
t.div(
|
||||
{ className: "field-help" },
|
||||
"The key is not stored on the server and it is used only for generating the signed JWT.",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
t.footer(
|
||||
{ className: "modal-footer" },
|
||||
t.button(
|
||||
{
|
||||
type: "button",
|
||||
className: "btn transparent m-r-auto",
|
||||
onclick: () => app.modals.close(modal),
|
||||
},
|
||||
t.span({ className: "txt" }, "Close"),
|
||||
),
|
||||
t.button(
|
||||
{
|
||||
"html-form": uniqueId + "_form",
|
||||
type: "submit",
|
||||
className: "btn expanded",
|
||||
},
|
||||
t.i({ className: "ri-key-line" }),
|
||||
t.span({ className: "txt" }, "Generate secret"),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return modal;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
window.app = window.app || {};
|
||||
window.app.oauth2 = window.app.oauth2 || {};
|
||||
|
||||
// note: data is the providerSettingsModal form store
|
||||
window.app.oauth2.lark = function(providerInfo, namePrefix, data) {
|
||||
const uniqueId = "lark_" + app.utils.randomString();
|
||||
|
||||
const domainOptions = [
|
||||
{ label: "Feishu (China)", value: "feishu.cn" },
|
||||
{ label: "Lark (International)", value: "larksuite.com" },
|
||||
];
|
||||
|
||||
const local = store({
|
||||
domain: data.config.authURL?.includes(domainOptions[1].value)
|
||||
? domainOptions[1].value
|
||||
: domainOptions[0].value,
|
||||
});
|
||||
|
||||
const watchers = [
|
||||
watch(() => local.domain, (domain) => {
|
||||
if (domain) {
|
||||
data.config.authURL = `https://accounts.${domain}/open-apis/authen/v1/authorize`;
|
||||
data.config.tokenURL = `https://open.${domain}/open-apis/authen/v2/oauth/token`;
|
||||
data.config.userInfoURL = `https://open.${domain}/open-apis/authen/v1/user_info`;
|
||||
}
|
||||
}),
|
||||
];
|
||||
|
||||
return t.div(
|
||||
{
|
||||
pbEvent: "oauth2LarkOptions",
|
||||
className: "oauth2-lark-options",
|
||||
onunmount: () => {
|
||||
watchers.forEach((w) => w?.unwatch());
|
||||
},
|
||||
},
|
||||
t.div(
|
||||
{ className: "grid" },
|
||||
t.div(
|
||||
{ className: "col-12" },
|
||||
t.div(
|
||||
{ className: "field" },
|
||||
t.label({ htmlFor: uniqueId + ".site" }, "Site"),
|
||||
app.components.select({
|
||||
options: domainOptions,
|
||||
required: true,
|
||||
value: () => local.domain || "",
|
||||
onchange: (selectedOpts) => {
|
||||
local.domain = selectedOpts?.[0]?.value;
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
t.div(
|
||||
{ className: "col-12" },
|
||||
t.div(
|
||||
{ className: "alert info" },
|
||||
"Note that the Lark user's ",
|
||||
t.strong(null, "Union ID"),
|
||||
" will be used for the association with the PocketBase user (see ",
|
||||
t.a({
|
||||
href:
|
||||
"https://open.feishu.cn/document/platform-overveiw/basic-concepts/user-identity-introduction/introduction#3f2d4b63",
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer",
|
||||
textContent: "Different Types of Lark User IDs",
|
||||
}),
|
||||
").",
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
window.app = window.app || {};
|
||||
window.app.oauth2 = window.app.oauth2 || {};
|
||||
|
||||
// note: data is the providerSettingsModal form store
|
||||
window.app.oauth2.microsoft = function(providerInfo, namePrefix, data) {
|
||||
const uniqueId = "microsoft_" + app.utils.randomString();
|
||||
|
||||
return t.div(
|
||||
{ pbEvent: "oauth2MicrosoftOptions", className: "oauth2-microsoft-options" },
|
||||
t.p({ className: "txt-bold" }, "Azure AD endpoints"),
|
||||
t.div(
|
||||
{ className: "grid" },
|
||||
t.div(
|
||||
{ className: "col-12" },
|
||||
t.div(
|
||||
{ className: "field" },
|
||||
t.label({ htmlFor: uniqueId + ".authURL" }, "Auth URL"),
|
||||
t.input({
|
||||
id: uniqueId + ".authURL",
|
||||
name: namePrefix + ".authURL",
|
||||
type: "url",
|
||||
required: true,
|
||||
value: () => data.config.authURL || "",
|
||||
oninput: (e) => data.config.authURL = e.target.value,
|
||||
}),
|
||||
),
|
||||
t.div(
|
||||
{ className: "field-help" },
|
||||
"Ex. https://login.microsoftonline.com/YOUR_DIRECTORY_TENANT_ID/oauth2/v2.0/authorize",
|
||||
),
|
||||
),
|
||||
t.div(
|
||||
{ className: "col-12" },
|
||||
t.div(
|
||||
{ className: "field" },
|
||||
t.label({ htmlFor: uniqueId + ".tokenURL" }, "Token URL"),
|
||||
t.input({
|
||||
id: uniqueId + ".tokenURL",
|
||||
name: namePrefix + ".tokenURL",
|
||||
type: "url",
|
||||
required: true,
|
||||
value: () => data.config.tokenURL || "",
|
||||
oninput: (e) => data.config.tokenURL = e.target.value,
|
||||
}),
|
||||
),
|
||||
t.div(
|
||||
{ className: "field-help" },
|
||||
"Ex. https://login.microsoftonline.com/YOUR_DIRECTORY_TENANT_ID/oauth2/v2.0/token",
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,234 @@
|
||||
window.app = window.app || {};
|
||||
window.app.oauth2 = window.app.oauth2 || {};
|
||||
|
||||
// note: data is the providerSettingsModal form store
|
||||
window.app.oauth2.oidc = function(providerInfo, namePrefix, data) {
|
||||
const uniqueId = "oidc_" + app.utils.randomString();
|
||||
|
||||
const userInfoOptions = [
|
||||
{ label: "User info URL", value: true },
|
||||
{ label: "ID Token", value: false },
|
||||
];
|
||||
|
||||
const local = store({
|
||||
useUserInfoUrl: false,
|
||||
});
|
||||
|
||||
const watchers = [];
|
||||
|
||||
return t.div(
|
||||
{
|
||||
pbEvent: "oauth2OIDCOptions",
|
||||
className: "oauth2-oidc-options",
|
||||
// init defaults
|
||||
onmount: (el) => {
|
||||
if (typeof data.config.displayName == "undefined") {
|
||||
data.config.displayName = "OIDC";
|
||||
}
|
||||
|
||||
if (typeof data.config.pkce == "undefined") {
|
||||
data.config.pkce = true;
|
||||
}
|
||||
|
||||
if (data.config.userInfoURL || !data.config.extra) {
|
||||
local.useUserInfoUrl = true;
|
||||
}
|
||||
|
||||
// unset the id_token or info url fields based on the toggle state
|
||||
watchers.push(
|
||||
watch(() => local.useUserInfoUrl, (useURL, oldUseURL) => {
|
||||
if (useURL) {
|
||||
// note: null because {} will just result in JSON unmarshal merge with the existing data
|
||||
data.config.extra = null;
|
||||
} else {
|
||||
data.config.userInfoURL = "";
|
||||
// note: fallback to empty object to distinguish from the null state since all id_token fields are optional
|
||||
data.config.extra = data.config.extra || {};
|
||||
}
|
||||
}),
|
||||
);
|
||||
},
|
||||
onunmount: () => {
|
||||
watchers.forEach((w) => w?.unwatch());
|
||||
},
|
||||
},
|
||||
t.div(
|
||||
{ className: "grid" },
|
||||
t.div(
|
||||
{ className: "col-12" },
|
||||
t.div(
|
||||
{ className: "field" },
|
||||
t.label({ htmlFor: uniqueId + ".displayName" }, "Display name"),
|
||||
t.input({
|
||||
id: uniqueId + ".displayName",
|
||||
name: namePrefix + ".displayName",
|
||||
type: "text",
|
||||
required: true,
|
||||
value: () => data.config.displayName || "",
|
||||
oninput: (e) => data.config.displayName = e.target.value,
|
||||
}),
|
||||
),
|
||||
),
|
||||
t.div(
|
||||
{ className: "col-12" },
|
||||
t.p({ className: "txt-bold" }, "Endpoints"),
|
||||
t.div(
|
||||
{ className: "field" },
|
||||
t.label({ htmlFor: uniqueId + ".authURL" }, "Auth URL"),
|
||||
t.input({
|
||||
id: uniqueId + ".authURL",
|
||||
name: namePrefix + ".authURL",
|
||||
type: "url",
|
||||
required: true,
|
||||
value: () => data.config.authURL || "",
|
||||
oninput: (e) => data.config.authURL = e.target.value,
|
||||
}),
|
||||
),
|
||||
),
|
||||
t.div(
|
||||
{ className: "col-12" },
|
||||
t.div(
|
||||
{ className: "field" },
|
||||
t.label({ htmlFor: uniqueId + ".tokenURL" }, "Token URL"),
|
||||
t.input({
|
||||
id: uniqueId + ".tokenURL",
|
||||
name: namePrefix + ".tokenURL",
|
||||
type: "url",
|
||||
required: true,
|
||||
value: () => data.config.tokenURL || "",
|
||||
oninput: (e) => data.config.tokenURL = e.target.value,
|
||||
}),
|
||||
),
|
||||
),
|
||||
// User info
|
||||
t.div(
|
||||
{ className: "col-12" },
|
||||
t.div(
|
||||
{ className: "field" },
|
||||
t.label({ htmlFor: uniqueId + ".userInfoSelect" }, "Fetch user info from"),
|
||||
app.components.select({
|
||||
id: uniqueId + ".userInfoSelect",
|
||||
required: true,
|
||||
options: userInfoOptions,
|
||||
value: () => local.useUserInfoUrl,
|
||||
onchange: (selectedOpts) => local.useUserInfoUrl = selectedOpts?.[0]?.value,
|
||||
}),
|
||||
),
|
||||
t.div({ className: "oidc-userinfo-options m-t-10" }, () => {
|
||||
if (local.useUserInfoUrl) {
|
||||
return t.div(
|
||||
{ className: "field" },
|
||||
t.label({ htmlFor: uniqueId + ".userInfoURL" }, "User info URL"),
|
||||
t.input({
|
||||
id: uniqueId + ".userInfoURL",
|
||||
name: namePrefix + ".userInfoURL",
|
||||
type: "url",
|
||||
required: true,
|
||||
value: () => data.config.userInfoURL || "",
|
||||
oninput: (e) => data.config.userInfoURL = e.target.value,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return t.div(
|
||||
{ className: "grid sm" },
|
||||
t.div(
|
||||
{ className: "col-12 txt-hint txt-sm" },
|
||||
t.em(
|
||||
null,
|
||||
"Both fields are considered optional because the parsed ",
|
||||
t.code(null, "id_token"),
|
||||
" is a direct result of the TLS code->token exchange server response.",
|
||||
),
|
||||
),
|
||||
t.div(
|
||||
{ className: "col-12" },
|
||||
t.div(
|
||||
{ className: "field" },
|
||||
t.label(
|
||||
{ htmlFor: uniqueId + ".extra.jwksURL" },
|
||||
t.span({ className: "txt" }, "JWKS verification URL"),
|
||||
t.i({
|
||||
ariaHidden: true,
|
||||
className: "ri-information-line link-hint",
|
||||
ariaDescription: app.attrs.tooltip(
|
||||
"URL to the public token verification keys.",
|
||||
),
|
||||
}),
|
||||
),
|
||||
t.input({
|
||||
id: uniqueId + ".extra.jwksURL",
|
||||
name: namePrefix + ".extra.jwksURL",
|
||||
type: "url",
|
||||
value: () => data.config.extra?.jwksURL || "",
|
||||
oninput: (e) => {
|
||||
data.config.extra = data.config.extra || {};
|
||||
data.config.extra.jwksURL = e.target.value;
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
t.div(
|
||||
{ className: "col-12" },
|
||||
t.div(
|
||||
{ className: "field" },
|
||||
t.label(
|
||||
{ htmlFor: uniqueId + ".extra.issuers" },
|
||||
t.span({ className: "txt" }, "Issuers"),
|
||||
t.i({
|
||||
ariaHidden: true,
|
||||
className: "ri-information-line link-hint",
|
||||
ariaDescription: app.attrs.tooltip(
|
||||
"Comma separated list of accepted values for the iss token claim validation.",
|
||||
),
|
||||
}),
|
||||
),
|
||||
t.input({
|
||||
id: uniqueId + ".extra.issuers",
|
||||
name: namePrefix + ".extra.issuers",
|
||||
type: "text",
|
||||
value: () => app.utils.joinNonEmpty(data.config.extra?.issuers),
|
||||
oninput: (e) => {
|
||||
const newValue = app.utils.splitNonEmpty(e.target.value, ",");
|
||||
const newStr = app.utils.joinNonEmpty(newValue);
|
||||
const oldStr = app.utils.joinNonEmpty(data.config.extra?.issuers);
|
||||
|
||||
// has an actual change
|
||||
if (oldStr != newStr) {
|
||||
data.config.extra = data.config.extra || {};
|
||||
data.config.extra.issuers = newValue;
|
||||
}
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
t.div(
|
||||
{ className: "col-12" },
|
||||
t.div(
|
||||
{ className: "field" },
|
||||
t.input({
|
||||
id: uniqueId + ".pkce",
|
||||
name: namePrefix + ".pkce",
|
||||
type: "checkbox",
|
||||
checked: () => data.config.pkce || false,
|
||||
onchange: (e) => data.config.pkce = e.target.checked,
|
||||
}),
|
||||
t.label(
|
||||
{ htmlFor: uniqueId + ".pkce" },
|
||||
t.span({ className: "txt", textContent: "Support PKCE" }),
|
||||
t.i({
|
||||
className: "ri-information-line link-hint",
|
||||
ariaHidden: true,
|
||||
ariaDescription: app.attrs.tooltip(
|
||||
"Usually it should be safe to be always enabled as most providers will just ignore the extra query parameters if they don't support PKCE.",
|
||||
),
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,115 @@
|
||||
window.app = window.app || {};
|
||||
window.app.components = window.app.components || {};
|
||||
|
||||
// note: data is the providerSettingsModal form store
|
||||
window.app.components.oauth2EndpointFields = function(providerInfo, namePrefix, data, settingsArg = {}) {
|
||||
const uniqueId = "endpoints_" + app.utils.randomString();
|
||||
|
||||
const settings = store({
|
||||
required: true,
|
||||
title: "Provider endpoints",
|
||||
});
|
||||
|
||||
const watchers = app.utils.extendStore(settings, settingsArg);
|
||||
|
||||
return t.div(
|
||||
{
|
||||
pbEvent: "oauth2Endpoints",
|
||||
className: "oauth2-endpoints",
|
||||
onunmount: () => {
|
||||
watchers.forEach((w) => w?.unwatch());
|
||||
},
|
||||
},
|
||||
t.p(
|
||||
{ className: "txt-bold" },
|
||||
(el) => {
|
||||
if (typeof settings.title == "function") {
|
||||
settings.title(el);
|
||||
}
|
||||
return settings.title;
|
||||
},
|
||||
),
|
||||
t.div(
|
||||
{ className: "grid" },
|
||||
t.div(
|
||||
{ className: "col-12" },
|
||||
t.div(
|
||||
{ className: "field" },
|
||||
t.label({ htmlFor: uniqueId + ".authURL" }, "Auth URL"),
|
||||
t.input({
|
||||
id: uniqueId + ".authURL",
|
||||
name: namePrefix + ".authURL",
|
||||
type: "url",
|
||||
required: () => !!settings.required,
|
||||
value: () => data.config.authURL || "",
|
||||
oninput: (e) => data.config.authURL = e.target.value,
|
||||
}),
|
||||
),
|
||||
),
|
||||
t.div(
|
||||
{ className: "col-12" },
|
||||
t.div(
|
||||
{ className: "field" },
|
||||
t.label({ htmlFor: uniqueId + ".tokenURL" }, "Token URL"),
|
||||
t.input({
|
||||
id: uniqueId + ".tokenURL",
|
||||
name: namePrefix + ".tokenURL",
|
||||
type: "url",
|
||||
required: () => !!settings.required,
|
||||
value: () => data.config.tokenURL || "",
|
||||
oninput: (e) => data.config.tokenURL = e.target.value,
|
||||
}),
|
||||
),
|
||||
),
|
||||
t.div(
|
||||
{ className: "col-12" },
|
||||
t.div(
|
||||
{ className: "field" },
|
||||
t.label({ htmlFor: uniqueId + ".userInfoURL" }, "User info URL"),
|
||||
t.input({
|
||||
id: uniqueId + ".userInfoURL",
|
||||
name: namePrefix + ".userInfoURL",
|
||||
type: "url",
|
||||
required: () => !!settings.required,
|
||||
value: () => data.config.userInfoURL || "",
|
||||
oninput: (e) => data.config.userInfoURL = e.target.value,
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
window.app.oauth2 = window.app.oauth2 || {};
|
||||
|
||||
window.app.oauth2.gitlab = function(providerInfo, namePrefix, data) {
|
||||
return app.components.oauth2EndpointFields(
|
||||
providerInfo,
|
||||
namePrefix,
|
||||
data,
|
||||
{
|
||||
required: false,
|
||||
title: "Self-hosted endpoints (optional)",
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
window.app.oauth2.gitea = function(providerInfo, namePrefix, data) {
|
||||
return app.components.oauth2EndpointFields(
|
||||
providerInfo,
|
||||
namePrefix,
|
||||
data,
|
||||
{
|
||||
required: false,
|
||||
title: "Self-hosted endpoints (optional)",
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
window.app.oauth2.mailcow = function(providerInfo, namePrefix, data) {
|
||||
return app.components.oauth2EndpointFields(
|
||||
providerInfo,
|
||||
namePrefix,
|
||||
data,
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user