web: Application wizard v2 with tests (#7004)

* A lot of comments about forms.

* Adding comments to the wizard.

* Broke out the text input into a single renderer.  Still works as required.

* web: Legibility in the ApplicationForm.

This is a pretty good result.  By using the LightDOM setting, this
provides the existing Authentik form manager with access to the
ak-form-horizontal-element components without having to do any
cross-border magic.  It's not ideal, and it shows up just how badly
we've got patternfly splattered everywhere, but the actual results
are remarkable.  The patterns for text, switch, radio, textarea,
file, and even select are smaller and easier here.

I'm still noodling on what an unspread search-select element would
look like.  It's just dependency injection, so it ought to be as
straightforward as that.

* web: Marking down the start of the 'components' library.

* web: Baby steps

I become frustrated with my inability to make any progress on this project, so I decided to reach
for a tool that I consider highly reliable but also incredibly time-consuming and boring: test
driven development.

In this case, I wrote a story about how I wanted to see the first page rendered: just put the HTML
tag, completely unadorned, that will handle the first page of the wizard. Then, add an event handler
that will send the updated content to some parent object, since what we really want is to
orchestrate the state of the user's input with a centralized location. Then, rather than fiddling
with the attributes and properties of the various pages, I wanted them to be able to "look up" the
values they want, much as we'd expect a standalone form to be able to pull its values from the
server, so I added a context object that receives the update event and incorporates the new
knowledge about the state of the process into itself.

The result is surprisingly satisfying: the first page renders cleanly, displays the content that we
want, and as we fiddle with, we can *watch in real time* as the results of the context are updated
and retransmitted to all receiving objects. And the sending object gets the results so it
re-renders, but it ends up looking the same as it was before the render.

* Now, it's starting to look like a complete package. The LDAP method is working, but there is a bug:
the radio is sending the wrong value !?!?!?. Track that down, dammit. The search wrappers now resend
their events as standard `input` events, and that actually seems to work well; the browser is
decorating it with the right target, with the right `name` attribute, and since we have good
definitions of the `value` as a string (the real value of any search object is its UUID4), that
works quite well. Added search wrappers for CoreGroup and CryptoCertificate (CertificateKeyPairs),
and the latter has flags for "use the first one if it's the only one" and "allow the display of
keyless certificates."

Not sure why `state()` is blocking the transmission of typing information from the typed element
to the context handler, but it's a bug in the typechecker, and it's not a problem so far.

* Now, it's starting to look like a complete package. The LDAP method is working, but there is a bug:
the radio is sending the wrong value !?!?!?. Track that down, dammit. The search wrappers now resend
their events as standard `input` events, and that actually seems to work well; the browser is
decorating it with the right target, with the right `name` attribute, and since we have good
definitions of the `value` as a string (the real value of any search object is its UUID4), that
works quite well. Added search wrappers for CoreGroup and CryptoCertificate (CertificateKeyPairs),
and the latter has flags for "use the first one if it's the only one" and "allow the display of
keyless certificates."

Not sure why `state()` is blocking the transmission of typing information from the typed element
to the context handler, but it's a bug in the typechecker, and it's not a problem so far.

* web: tracked down that weirld bug with the radio.

Because radio inputs are actually multiples, the events handling for
radio is... wonky.  If we want our `<ak-radio>` component to be a
unitary event dispatcher, saying "This is the element selected," we
needed to do more than what was currently being handled.

I've intercepted the events that we care about and have placed
them into a controller that dictates both the setting and the
re-render of the component.  This makes it "controlled" (to use the
Angular/React/Vue) language and depends on Lit's reactiveElement
lifecycle to work, rather than trust the browser, but the browser's
experience with respect to the `<input type=radio` is pretty bad:
both input elements fire events, one for "losing selection" and
one for "gaining selection".  That can be very confusing to handle,
so we funnel them down in our aggregate radio element to a single
event, "selection changed".

As a quality-of-life measure, I've also set the label to be
unselectable; this means that a click on the label will trigger the
selection event, and a long click will not disable selection or
confuse the selection event generator.

* web: now passing the precommit phase

* web: a HACK for Storybook to inject the "use light theme" flag into the body.

This isn't really a very good hack; what it does is say that every story is
responsible for hacking its theme into the parent.  This is very annoying, but
it does mean that we can at least show our components in the best light.

* web: ak-application-wizard-authentication-by-oauth, and many fixes!

1. Fixed `eventEmitter` so that if the detail object is a scalar, it will not attempt to "objectify"
   it.  This was causing a bug where retrofitting the eventEmitter to some older components resulted
   in a detail of "some" being translated into ['s', 'o', 'm', 'e'].  Not what is wanted.
2. Removed the "transitional form" from the existing components; they had a two-step where the web
   component class was just a wrapper around an independent rendering function.  While this worked,
   it was only to make the case that they *were* independent rendering objects and could be
   supported with the right web component framework.  We're halfway there now; the last step will be
   to transform the horizontal-element and various input CSS into componentized CSS, the way
   Patternfly-Elements is currently doing.
3. Fixed the `help` field so that it could take a string or a TemplateResult, and if the latter,
   don't bother wrapping it in the helper text functionality; just let it be its own thing.  This
   supports the multi-line help of redirectURI as well as the `ak-utils-time-delta` capability.
4. Transform Oauth2ProviderForm to use the new components, to the best of our ability.  Also used
   the `provider = this.wizard.provider` and `provider = this.instance` syntax to make the render
   function *completely portable*; it's the exact same text that is dropped into...
5. The complete `ak-application-wizard-authentication-by-oauth` component. They're so similar part
   of me wonders if I could push them both out to a common reference, or a collection of common
   references.  Both components use the PropertyMapping and Sources, and both use the same
   collection of searches (Crypto, Flow).
6. A Storybook for `ak-application-wizard-authentication-by-oauth`, showing the works working.
7. New mocks for `authorizationFlow`, `propertyMappings`, and `hasJWKs`.

This sequence has revealed a bug in the radio control.  (It's always the radio control.)  If the
default doesn't match the current setting, the radio control doesn't behave as expected; it won't
change when you fully expect that it should.  I'll investigate how to harmonize those tomorrow.

* web: Converted our toggle groups to a more streamlined implementation.

* web: one more toggle group.

* initial api and schema

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* separate blueprint importer from yaml parsing

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* cleanup

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* web: Replace ad-hoc toggle control with ak-toggle-group

This commit replaces various ad-hoc implementations of the Patternfly Toggle Group HTML with a web
component that encapsulates all of the needed behavior and exposes a single API with a single event
handler, return the value of the option clicked.

The results are: Lots of visual clutter is eliminated.  A single link of:

```
<div class="pf-c-toggle-group__item">
  <button
      class="pf-c-toggle-group__button ${this.mode === ProxyMode.Proxy
          ? "pf-m-selected"
          : ""}"
      type="button"
      @click=${() => {
          this.mode = ProxyMode.Proxy;
      }}>
      <span class="pf-c-toggle-group__text">${msg("Proxy")}</span>
  </button>
</div>
<div class="pf-c-divider pf-m-vertical" role="separator"></div>
```

Now looks like:

```
<option value=${ProxyMode.Proxy}>${msg("Proxy")}</option>
```

This also means that the three pages that used the Patternfly Toggle Group could eliminate all of
their Patternfly PFToggleGroup needs, as well as the `justify-content: center` extension, which also
eliminated the `css` import.

The savings aren't as spectacular as I'd hoped: removed 178 lines, but added 123; total savings 55
lines of code.  I still count this a win: we need never write another toggle component again, and
any bugs, extensions or features we may want to add can be centralized or forked without risking the
whole edifice.

* web: minor code formatting issue.

* add new "must_created" state to blueprints to prevent overwriting objects

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* web: adding a storybook for the ak-toggle-group component

* Bugs found by CI/CD.

* web: Replace ad-hoc search for CryptoCertificateKeyPairs with ak-crypto-certeficate-search

This commit replaces various ad-hoc implementations of `search-select` for CryptoCertificateKeyPairs
with a web component that encapsulates all of the needed behavior and exposes a single API.

The results are: Lots of visual clutter is eliminated.  A single search of:

```HTML
<ak-search-select
    .fetchObjects=${async (query?: string): Promise<CertificateKeyPair[]> => {
        const args: CryptoCertificatekeypairsListRequest = {
            ordering: "name",
            hasKey: true,
            includeDetails: false,
        };
        if (query !== undefined) {
            args.search = query;
        }
        const certificates = await new CryptoApi(
            DEFAULT_CONFIG,
        ).cryptoCertificatekeypairsList(args);
        return certificates.results;
    }}
    .renderElement=${(item: CertificateKeyPair): string => {
        return item.name;
    }}
    .value=${(item: CertificateKeyPair | undefined): string | undefined => {
        return item?.pk;
    }}
    .selected=${(item: CertificateKeyPair): boolean => {
        return this.instance?.tlsVerification === item.pk;
    }}
    ?blankable=${true}
>
</ak-search-select>
```

Now looks like:

```HTML
<ak-crypto-certificate-search certificate=${this.instance?.tlsVerification}>
</ak-crypto-certificate-search>
```

There are three searches that do not require there to be a valid key with the certificate; these are
supported with the boolean property `nokey`; likewise, there is one search (in SAMLProviderForm)
that states that if there is no current certificate in the SAMLProvider and only one certificate can
be found in the Authentik database, use that one; this is supported with the boolean property
`singleton`.

These changes replace 382 lines of object-oriented invocations with 36 lines of declarative
configuration, and 98 lines for the class.  Overall, the code for "find a crypto certificate" has
been reduced by 46%.

Suggestions for a better word than `singleton` are welcome!

* web: display tests for CryptoCertificateKeypair search

This adds a Storybook for the CryptoCertificateKeypair search, including
a mock fetch of the data.  In the course of running the tests, we discovered
that including the SearchSelect _class_ won't include the customElement declaration
unless you include the whole file!  Other bugs found: including the CSS from
Storybook is different from that of LitElement native, so much so that the
adapter needed to be included.  FlowSearch had a similar bug.  The problem
only manifests when building via Webpack (which Storybook uses) and not
Rollup, but we should support both in distribution.

* Fixed behavioral problem with the radio; the `if` there was
preventing the radio from reflecting the default correctly.
The observed behavior was that the radio wouldn't "activate"
until the item selected during the render pass was clicked on
first.

* Proxy Provider done.

* web: Tactical change.  Put all the variants on the second page; it's
a longer list, but it's also easier to manage than all those
required sub-options.

* Rounding out the catalog.

* web: SAML Manual Configuration

Added a 'design document' that just kinda describes what I'm trying
to do, in case I don't get this done by Friday Aug 11, 2023.

I had two tables doing the same thing, so I merged them and then
wrote a few map/filters to specialize them for those two use cases.

Along the way I had to fiddle with the ESLint settings so that
underscore-prefixed unused variables would be ignored.

I cleaned up the visual appeal of the forms in the LDAP application.

I was copy/pasting the "handleProviderEvent" function, so I pulled
it out into ApplicationWizardProviderPageBase.  Not so much a matter
of abstraction as just disliking that kind of duplication; it served
no purpose.

* Added SAML Story to Storybook.

* Web: This is coming together amazingly well.  Like, almost too well.

* web: 80% of the way there

This commit includes the first three pages of the wizard, the
completion of the wizard framework with evented handling, and control
over progression.

Some shortcomings of this design have become evident: it isn't
possible to communicate between the steps' wrappers, as they are
POJOs without access to the context.  An imperative decision-making
process has to be inserted in the orchestration layer,
which is kinda annoying.

But it looks good and it behaves correctly, to the extent that I've
given it behavior.  It's an excellent foundation.

* Linting.

* web: application wizard

Found where the hook for form validity should go.  Excellent!  Now I just need to incorporate
that basic validation into the business logic and we're good to go.

* Turns out that was one layer too many; the topmost component was fine for
maintaining the context.

* It looks like my brilliant strategy has hit a snag.

The idea is simple.  Let's start with this picture:

```
<application-wizard .steps=${[... a collection of step objects ...]}>
  <wizard-main .steps=${(steps from above)}>
    <application-current-panel>
      <current-form>
```

- ApplicationWizard has a Context for the ApplicationProviderPair (or whatever it's going to be).
  This context does not know about the steps; it just knows about: the "application" object, the
  "provider" object, and a discriminator to know *which* provider the user has selected.
- ApplicationWizard has Steps that, among other things, provides Panels for:
  - Application
  - Pick Provider
  - Configure Provider
  - Submit ApplicationProviderPair to the back-end
- The WizardFrame renders the CurrentPanel for the CurrentStep

The CurrentPanel gets its data from the ApplicationWizard in the form of a Context. It then sends
messages (events) to ApplicationWizard about the contents of each field as the user is filling out
the form, so that the ApplicationWizard can record those in the ApplicationProviderPair for later
submission.

When a CurrentForm is valid, the ApplicationWizard updates the Steps object to show that the "Next
button" on the Wizard is now available.

In this way, the user can progress through the system.  When they get to the last page, we can
provide in the ApplicationWizard with the means to submit the form and/or send the user back to
the page with the validation failure.

Problem: The context is being updated in real-time, which is triggering re-renders of the form. This
leads to focus problems as the fields that are not yet valid are triggering "focus grab" behavior.
This is a classic problem with "controlled" inputs. What we really want is for the CurrentPanel to
not re-render at all, but to behave like a normal, uncontrolled form, and let the browser do most of
the work.  We still want the [Next] button to enable when the form is valid enough to permit that.

---

Other details: I've ripped out a lot of Jen's work, which is probably a mistake.  It's still
preserved elsewhere.  I've also cleaned up the various wizardly things to try and look organized.
It *looks* like it should work, it just... doesn't.  Not yet.

* Late addition: I had an inspiration about how to reduce the way
reactivity broke focus by, basically, removing the reactivity and
managing the first-time-through lifecycle to prevent the update
from causing refocus.  It works well!  Now I just need to test it.

* This application fixes the bug with respect to the wizard-level context being updated incorrectly.

Understandings:

- To use uncontrolled inputs, which I prefer, the context object should not be a state or property
  at the level of consumers; it should not automatically re-render with every keystroke, i.e. "The
  React Way."  We're using Web Components, [client-side
  validation](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation) exists on the
  platform already, and live-validation is problematic for any number of reasons.
- The trade-off is that it is now necessary to re-render the target page of the wizard de-novo, but
  that's not really as big a deal as it sounds. Lit is ready to do that... and then nothing else
  until we request a change-of-page. Excellent.
- The top level context *must* be a state, but it's better if it's a state never actually used by
  the top-level context container. The debate about whether or not to make that container a dumb one
  (`<slot></slot>`) or to merge it with the top-level object continues; here, I've merged it with
  the top-level wizard object, but that object does not refer to the state variable being managed in
  its render pass, so changes to it do not cause a re-render of the whole wizard. The purpose of the
  top-level page is to manage the *steps*, not the *content of any step*. A step may change
  dynamically based on the content of a step, but that's the same thing as *which step*. Lesson:
  always know what your state is *about*.
- Deep merging is a complex subject, but here it's appropriate to our needs.

* web: Application Wizard

This commit combines a working (but very unpolished) version of the Application Wizard with Jen's
code for the CoreTransactionApplicationRequest, resulting in a successful round trip.

It fixes a number of bugs with the way ContextProducer decorators were being processed, such that
they just weren't working with our current configuration (although they did work fine in Storybook);
consumers didn't need to be fixed.

It also *removes* the steps-aware context from the Wizard.

That *may* be a mistake.  To re-iterate, the `WizardFrame` provides the chrome for a Wizard: the
button bar div, the breadcrumbs div, the header div, and it takes the steps object as its source of
truth for all of the content.  The `WizardContent` part of the application has two parts: The
`WizardMain`, which wraps the frame and supplies the context for all the `WizardPanels`, and the
`WizardPanels` themselves, which are dependent on a context from `WizardMain` for the data that
populates each panel. YAGNI right now that the panels need to know anything about the steps, and the
`WizardMain` can just pass a fresh `.steps` object to the `WizardFrame` when they need updating.
Using props drilling may make more sense here.

It certainy does *not* make sense for the panels.  They need to be renderable on-demand, and they
need to make sense of what they're rendering on-demand, so the function is

```
(panel code) => (context) => (rendered panel)
```

(Yes, that's curried notation. Deal.)

* This commit includes the first WDIO test for the ApplicationWizard.  It doesn't do much right now, but
it does log in and navigate to the wizard successfully.

* web: completed test for single application, provided new programming language to make it easier to write tests.

* Almost there.

Missing: The validation is currently not working as expected, and I cannot get the backend
to give me meaningful data helping us "go back" to the field that wasn't valid.  I really
don't want to put all the meaningful validation on the front-end; that's the road to -
perdition, the back-end must be usable by people less assiduous than we are.

Also: Need to make the button bar work better; maybe each panel can provide a custom button
bar if one is needed?

* web: Test harness

We have an end-to-end test harness that includes a trivially correct DSL for "This is what a user would do, do this":

```
const deleteProvider = (theSlug) => ([
    ["button", '>>>ak-sidebar-item a[href="#/core/providers"]'],
    ["deletebox", `>>>a[href="#/core/applications/${theSlug}"]`],
    ["button", '>>>ak-forms-delete-bulk button[slot="trigger"]'],
    ["button", '>>>ak-forms-delete-bulk div[role="dialog"] ak-spinner-button'],
]);
```

It's now possible to target individual sequences of events this way.  With a little creativity, we could have standalone functions that take parameters for our calls and just do them, without too much struggle.

* web: Revised navigation

After working with the navigation for awhile, I realized that it's a poor map; what I really wanted was
a controller/view pair, where events flow up to the controller and then messages on "what to draw" flow
down to the view.  It work quite well, and the wizard frame is smaller and smarter for it.

I've also moved the WDIO-driven tests into the 'tests' folder, because it (a) makes more sense to put
them there, and (b) it prevents any confusion about who's in charge of node_modules.

* web: Simplify, simplify, simplify

Sort-of.

This commit changes the way the "wizard step coordinator" layer works, giving the
wizard writer much more power over button bar.  It still assumes there are only
three actions the wizard frame wants to commit: next, back, and close.  This empowers
the steps themselves to re-arrange their buttons and describe the rules through which
transitions occur.

* web: resetting the form is not working yet...

I vehemently dislike the object-oriented "reset" command; every wizard should start with
an absolutely fresh copy of the data upon entry.  Refactoring the wizard to re-build its
content from the inside is the correct way to go, but I don't have a good mental image
of how to make the ModalButton and the component it invokes interact cleanly, which
frustrates the hell out of me.

* web: reset

As I said, I greatly dislike having to be dependent upon "resets"; I prefer my
data to be de novo going into a "new" transaction.  That said, we work with
what we've got; I've created an event generated by the wizard that says the
modal just closed; anything wrapping and implementing the wizard can then
capture that event and reset the data.  I've also added a pair of functions
that create the two states (what step, what form data) anew, so that resetting
is as trivial as initializing (and is exactly the same, code-wise).

* web: Without error handling, this is complete, but I still need @BeryJu (Jens)
for help with the SAML Upload (it doesn't appear to be correctly handled?) and
the error handling.

* web: revise tests for wizard

This commit replaces the previous WDIO instance with a more formal and straightforward process using
the [pageobjects](https://martinfowler.com/bliki/PageObject.html).  In this form, every major
component has its own test suite, and a test is a sequence of exercises of those components.

A test then becomes something as straightforward as:

```
        await LoginPage.open();
        await LoginPage.login("ken@goauthentik.io", "eat10bugs");

        expect(await UserLibraryPage.pageHeader).toHaveText("My Applications");
        await UserLibraryPage.goToAdmin();

        expect(await AdminOverviewPage.pageHeader).toHaveText("Welcome, ");
        await AdminOverviewPage.openApplicationsListPage();

        expect(await ApplicationsListPage.pageHeader).toHaveText("Applications");
        ApplicationsListPage.startCreateApplicationWizard();

        await ApplicationWizard.app.name.setValue(`Test application ${newId}`);
        await ApplicationWizard.nextButton.click();
        await (await ApplicationWizard.getProviderType("ldapprovider")).click();
        await ApplicationWizard.nextButton.click();
        await ApplicationWizard.ldap.setBindFlow("default-authentication-flow");
        await ApplicationWizard.nextButton.click();
        await expect(await ApplicationWizard.commitMessage).toHaveText(
            "Your application has been saved"
        );
```

Whether or not there's another layer of DSL in there or not, this is a pretty nice idiom for
maintaining tests.

* web: updating with forms and fixes for eslint complaints.

* web/add webdriverIO testing layer

This commit adds WebdriverIO as an end-to-end solution to unit testing.  WebdriverIO can be run both
locally and remotely, supports strong integration with web components, and is generally robust for
use in pipelines.  I'll confess to working through a tutorial on how to do this for web components,
and this is just chapter 2 (I think there are 5 or so chapters...).

There's a makefile, with help!  If you just run `make` it tells you:

```
Specify a command. The choices are:

  help                 Show this help
  node_modules         Runs `npm install` to prepare this feature
  precommit            Run the precommit: spell check all comments, eslint with sonarJS, prettier-write
  test-good-login      Test that we can log into the server. Requires a running instance of the server.
  test-bad-login       Test that bad usernames and passwords create appropriate error messages
```

... because Makefiles are documentation, and documentation belongs in Makefiles.

I've chosen to go with a PageObject-oriented low-level DSL; what that means is that for each major
components (a page, a form, a wizard), there's a class that provides human-readable names for
human-interactable and human-viewable objects on the page.  The LoginPage object, for example, has
selectors for the username, password, submit button, and the failure alert; accessing those allows
us to test for items as expected., and to write a DSL for "a good login" that's as straightforward
as:

```
        await LoginPage.open();
        await LoginPage.login("ken@goauthentik.io", "eat10bugs");
        await expect(UserLibraryPage.pageHeader).toHaveText("My applications");
```

There was a *lot* of messing around with the LoginPage to get the username and password into the
system.  For example, I had to do this with all the `waitForClickable` and `waitForEnable` because
we both keep the buttons inaccessible until the form has something and we "black out" the page (put
a darkening filter over it) while accessing the flow, meaning there was a race condition such that
the test would attempt to interact with the username or password field before it was accessible.
But this works now, which is very nice.

``` JavaScript
    get inputUsername() {
        return $('>>>input[name="uidField"]');
    }

    get btnSubmit() {
        return $('>>>button[type="submit"]');
    }

    async username(username: string) {
        await this.inputUsername.waitForClickable();
        await this.inputUsername.setValue(username);
        await this.btnSubmit.waitForEnabled();
        await this.btnSubmit.click();
    }
```

The bells & whistles of *Prettier*, *Eslint*, and *Codespell* have also been enabled. I do like my
guardrails.

* web/adding tests: added comments and cleaned up some administrative features.

* web/test: changed the name of one test to reflect it's 'good' status

* core/allow alternative postgres credentials

This commit allows the `dev-reset` command in the Makefile to pick up and use credentials from the
`.env` file if they are present, or fallback to the defaults provided if they are not. This is the
only place in the Makefile where the database credentials are used directly against postgresql
binaries. The syntax was tested with bash, zsh, and csh, and did not fail under those.

The `$${:-}` syntax is a combination of a Makefile idiom for "Pass a single `$` to the environment
where this command will be executed," and the shell expresion `${VARIABLE:-default}` means
"dereference the environment variable; if it is undefined, used the default value provided."

* Re-arrange sequence to avoid recursive make.

Nothing wrong with recursive make; it just wasn't essential
here.  `migrate` is just a build target, not a task.

* Cleanup according to the Usage:
  checkmake [options] <makefile>...
  checkmake -h | --help
  checkmake --version
  checkmake --list-rules Makefile linting tool.

* core: added 'help' to the Makefile

* get postgres config from authentik config loader

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* don't set -x by default

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* sort help

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* update help strings

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* web: test LDAP wizard sequence

* web: improve testing by adding test admin user via blueprint

* This commit continues the application wizard buildout.  In this commit are the following changes:

- Added SCIM to the list of available providers
- Fixed ForwardProxy so that its mode is set correctly.  (This is a special case in the committer;
  I'm unhappy with that.)
- Fixed the commit messages so that:
  - icons are set correctly (Success, Danger, Working)
  - icons are colored correctly according to state
  - commit message includes a `data-commit-state` field so tests can find it!
- Merged the application wizard tests into a single test pass
- Isolated common parts of the application wizard tests to reduce unnecessary repetition.  All
  application tests are the same until you reach the provider section anyway.
- Fixed the unit tests so they're finding the right error messages and are enabled to display them
  correctly.
- Moved the test Form handlers into their own folder so they're not cluttering up the Pages folder.

* web: add radius to application wizard

This commit continues the application wizard buildout.  In this commit are the following changes:

- Fixed a width-setting bug in the Makefile `make help` feature (i.e "automate that stuff!")
- Added Radius to the list of providers we can offer via the wizard
- Added `launchUrl` and `UI Settings` to features of the application page the wizard can find
- Changed 'SAML Manual Configuration' to just say "SAML Configuration"
- Modified `ak-form-group` to take and honor the `aria-label` property (which in turn makes it
  easier to target specific forms with unit testing)
- Reduced the log level for wdio to 'warn'; 'info' was super-spammy and not helpful.  It can be put
  back with `--logLevel info` from the command line.

* fix blueprints

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* update package name

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add dependabot

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* prettier run

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add basic CI

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove hooks

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* web: application wizard refactor & completion

This commit refactors the various components of the Wizard and ApplicationWizard, creating a much
more maintainable and satisfying Wizard experience for both developers (i.e, *me* and *Jens* so
far), and for the customer.

The Wizard base has been refactored into three components:

**AkWizardController**

The `AkWizardController` provides the event listenters for the wizard; it hooks them up, recevies the
events, and forwards them to the wizard.  It unwraps the event objects and forwards the relevant
messages contained in the events.  It knows of three event categories:

- Navigation requests (move to a different step)
- Update requests (the current step has updated the business content)
- Close requests (close or cancel the wizard).

**ak-wizard-frame**

The `ak-wizard-frame` is the ModalButton interface.  It provides the Header, Breadcrumbs (nee`
"navigation block"), Buttons, and a DIV into which the main content is rendered.

**AkWizard**

`AkWizard` is an *incomplete* implementation of the wizard. It's meant to be inherited by a child
class, which will implement the rest. It extends `AKElement`. It provides the basic content needed,
such as steps, currentStep (as an index), an accessor for the step itself, an accessor for the
frame, and the interface to the `AkWizardController`.

**ApplicationWizard**

The `ApplicationWizard` itself has been refactored to accommodate these changes. It inherits from
`AkWizard` and provides the business logic for what to do when a form updates, some custom logic for
preventing moving through the wizard when the forms are incomplete, and a persistence layer for
filling out different providers in the same session. It's simplified a *lot*.

The types specified for `AkWizard` are pretty nifty, I think. I could wish the types being passed
via the custom events were more robust, but [strongly typed custom
events](https://github.com/lit/lit-element/issues/808) turn out to be quite the pain in the, er,
neck. As it is, the `precommit` pass did very good at preventing the worst disasters.

The steps themselves were re-written as objects so that they could take advantage of their `valid`
and `disabled` states and provide more meaningful buttons and labels. I think it's a solid
compromise, and it moved a lot of display logic out of the core `handleUpdate()` business method.

The tests, such as they are, are passing.

* Added comment describing new test.

* web: ensuring copy from `main` is canon

* web: fixes after merge

* web: laying the groundwork for future expansion

This commit is a hodge-podge of updates and changes to the web.  Functional changes:

- Makefile: Fixed a bug in the `help` section that prevented the WIDTH from being accurately
  calculated if `help` was included rather than in-lined.

- ESLint: Modified the "unused vars" rule so that variables starting with an underline are not
  considered by the rule.  This allows for elided variables in event handlers.  It's not a perfect
  solution-- a better one would be to use Typescript's function-specialization typing, but there are
  too many places where we elide or ignore some variables in a function's usage that switching over
  to specialization would be a huge lift.

- locale: It turns out, lit-locale does its own context management.  We don't need to have a context
  at all in this space, and that's one less listener we need to attach t othe DOM.

- ModalButton: A small thing, but using `nothing` instead of "html``" allows lit better control over
  rendering and reduces the number of actual renders of the page.

- FormGroup: Provided a means to modify the aria-label, rather than stick with the just the word
  "Details."  Specializing this field will both help users of screen readers in the future, and will
  allow test suites to find specific form groups now.

- RadioButton: provide a more consistent interface to the RadioButton.  First, we dispatch the
  events to the outside world, and we set the value locally so that the current `Form.ts` continues
  to behave as expected.  We also prevent the "button lost value" event from propagating; this
  presents a unified select-like interface to users of the RadioButtonGroup.  The current value
  semantics are preserved; other clients of the RadioButton do not see a change in behavior.

- EventEmitter: If the custom event detail is *not* an object, do not use the object-like semantics
  for forwarding it; just send it as-is.

- Comments: In the course of laying the groundwork for the application wizard, I throw a LOT of
  comments into the code, describing APIs, interfaces, class and function signatures, to better
  document the behavior inside and as signposts for future work.

* web: permit arrays to be sent in custom events without interpolation.

* actually use assignValue or rather serializeFieldRecursive

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* web: eslint & prettier fixes, plus small aesthetic differences.

* Restoring this file.  Not sure where it disappears to.

* fix label in dark mode

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* SCIM Manuel -> SCIM

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix lint errors

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* web: better converter configuration, CSS repair, and forward-domain-proxy

1. Forward Domain Proxy.  I wasn't sure if this method was appropriate for the wizard,
   but Jens says it is.  I've added it.

2. In the process of doing so, I decided that the Provider.converter field was overly
   complexified; I tried too hard to reduce the number of functions I needed to define,
   but in the process outsourced some of the logic of converting the Wizard's dataset
   into a property typed request to the `commit` phase, which was inappropriate.  All
   of the logic about a provider, aside from its display, should be here with the code
   that distinguishes between providers.  This commit makes it so.

3. Small CSS fix: the fields inherited from the Proxy provider forms had some unexpected
   CSS which was causing a bit of a weird indent.  That has been rectified.

* web: running pre-commit after merge.

* web: ensure the applications wizard tests finish after current changes

* prettier has opinions.

* web: application wizard spit & polish

The "ApplicationWizardHint" now correctly uses the localstorage and allows the user to navigate back
and see the message after it's been hidden, so that it will always be available during the test
phase.

The ApplicationList's old "Create Application Form" button has been restored for the purposes of the
test phase.

The ApplicationWizard is now available on both the ApplicationList and ProviderList pages.

Tana and I discussed the microcopy, putting a stronger second-person "You can do..." twist onto the
language, to give the user the sense of empowerment.

The ShowHintController now has both "hide" and "show" operations, to support the hint restoration.

* web: updated storybook stories for the wizard, illustration how "a simple wizard" is configured in source code and tested with storybook.

* web: I hate getting spanked by prettier.

* web: sometimes I wish I had lower standards

Anyway, this was a very stupid bug, because by definition function
definition arguments don't have uses, they're being defined, not
implemented.  Fixed, conf fixed to compensate, and consequences
conquered.

* move context from labs to main

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* Revert "move context from labs to main"

This reverts commit 3718ee6904.

* web: reify the data loop

I was very unhappy with the "update this dot-path" mechanism I was using earlier; it was hard
for me to read and understand what was happening, and I wrote the darned thing.  I decided instead
to go with a hard substitution model; each phase of the wizard is responsible for updating the
*entire* payload, mostly by creating a new payload and substituting the field value associated
with the event.

On the receiver, we have to do that *again* to handle the swapping of providers when the user
chooses one and then another.  It looks clunky, and it is, but it's *legible*; a junior dev
could understand what it's doing, and that's the goal.

* Revert "web: reify the data loop"

This reverts commit 09fedcacf0.

* web: revert the 'lit' to 'lit-labs' for task and context.

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Ken Sternberg 2023-10-18 12:43:37 -07:00 committed by GitHub
parent 3beb421e50
commit 3a7283c670
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
80 changed files with 5552 additions and 2625 deletions

View file

@ -15,6 +15,15 @@
"linebreak-style": ["error", "unix"],
"quotes": ["error", "double", { "avoidEscape": true }],
"semi": ["error", "always"],
"@typescript-eslint/ban-ts-comment": "off"
"@typescript-eslint/ban-ts-comment": "off",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"caughtErrorsIgnorePattern": "^_"
}
]
}
}

View file

@ -36,3 +36,6 @@ test-good-login: node_modules admin-user ## Test that we can log into the serve
test-bad-login: node_modules admin-user ## Test that bad usernames and passwords create appropriate error messages
$(SPEC)/bad-logins.ts
test-application-wizard: node_modules admin-user ## Test that the application wizard works as expected
$(SPEC)/new-application-by-wizard.ts

View file

@ -0,0 +1,75 @@
import AdminPage from "./admin.page.js";
import ApplicationForm from "./forms/application.form.js";
import ForwardProxyForm from "./forms/forward-proxy.form.js";
import LdapForm from "./forms/ldap.form.js";
import OauthForm from "./forms/oauth.form.js";
import RadiusForm from "./forms/radius.form.js";
import SamlForm from "./forms/saml.form.js";
import ScimForm from "./forms/scim.form.js";
import TransparentProxyForm from "./forms/transparent-proxy.form.js";
import { $ } from "@wdio/globals";
/**
* sub page containing specific selectors and methods for a specific page
*/
class ApplicationWizardView extends AdminPage {
/**
* define selectors using getter methods
*/
ldap = LdapForm;
oauth = OauthForm;
transparentProxy = TransparentProxyForm;
forwardProxy = ForwardProxyForm;
saml = SamlForm;
scim = ScimForm;
radius = RadiusForm;
app = ApplicationForm;
get wizardTitle() {
return $(">>>ak-wizard-frame .pf-c-wizard__header h1.pf-c-title");
}
get providerList() {
return $(">>>ak-application-wizard-authentication-method-choice");
}
get nextButton() {
return $(">>>ak-wizard-frame footer button.pf-m-primary");
}
async getProviderType(type: string) {
return await this.providerList.$(`>>>input[value="${type}"]`);
}
get successMessage() {
return $('>>>[data-commit-state="success"]');
}
}
type Pair = [string, string];
// Define a getter for each provider type in the radio button collection.
const providerValues: Pair[] = [
["oauth2provider", "oauth2Provider"],
["ldapprovider", "ldapProvider"],
["proxyprovider-proxy", "proxyProviderProxy"],
["proxyprovider-forwardsingle", "proxyProviderForwardsingle"],
["radiusprovider", "radiusProvider"],
["samlprovider", "samlProvider"],
["scimprovider", "scimProvider"],
];
providerValues.forEach(([value, name]: Pair) => {
Object.defineProperties(ApplicationWizardView.prototype, {
[name]: {
get: function () {
return this.providerList.$(`>>>input[value="${value}"]`);
},
},
});
});
export default new ApplicationWizardView();

View file

@ -0,0 +1,21 @@
import AdminPage from "./admin.page.js";
import { $ } from "@wdio/globals";
/**
* sub page containing specific selectors and methods for a specific page
*/
class ApplicationsListPage extends AdminPage {
/**
* define selectors using getter methods
*/
get startWizardButton() {
return $('>>>ak-wizard-frame button[slot="trigger"]');
}
async open() {
return await super.open("if/admin/#/core/applications");
}
}
export default new ApplicationsListPage();

View file

@ -0,0 +1,18 @@
import Page from "../page.js";
import { $ } from "@wdio/globals";
export class ApplicationForm extends Page {
get name() {
return $('>>>ak-form-element-horizontal input[name="name"]');
}
get uiSettings() {
return $('>>>ak-form-group button[aria-label="UI Settings"]');
}
get launchUrl() {
return $('>>>input[name="metaLaunchUrl"]');
}
}
export default new ApplicationForm();

View file

@ -0,0 +1,18 @@
import Page from "../page.js";
import { $ } from "@wdio/globals";
export class ForwardProxyForm extends Page {
async setAuthorizationFlow(selector: string) {
await this.searchSelect(
'>>>ak-flow-search[name="authorizationFlow"] input[type="text"]',
"authorizationFlow",
`button*=${selector}`,
);
}
get externalHost() {
return $('>>>input[name="externalHost"]');
}
}
export default new ForwardProxyForm();

View file

@ -0,0 +1,13 @@
import Page from "../page.js";
export class LdapForm extends Page {
async setBindFlow(selector: string) {
await this.searchSelect(
'>>>ak-tenanted-flow-search[name="authorizationFlow"] input[type="text"]',
"authorizationFlow",
`button*=${selector}`,
);
}
}
export default new LdapForm();

View file

@ -0,0 +1,13 @@
import Page from "../page.js";
export class RadiusForm extends Page {
async setAuthenticationFlow(selector: string) {
await this.searchSelect(
'>>>ak-tenanted-flow-search[name="authorizationFlow"] input[type="text"]',
"authorizationFlow",
`button*=${selector}`,
);
}
}
export default new RadiusForm();

View file

@ -0,0 +1,18 @@
import Page from "../page.js";
import { $ } from "@wdio/globals";
export class SamlForm extends Page {
async setAuthorizationFlow(selector: string) {
await this.searchSelect(
'>>>ak-flow-search[name="authorizationFlow"] input[type="text"]',
"authorizationFlow",
`button*=${selector}`,
);
}
get acsUrl() {
return $('>>>input[name="acsUrl"]');
}
}
export default new SamlForm();

View file

@ -0,0 +1,13 @@
import Page from "../page.js";
export class ScimForm extends Page {
get url() {
return $('>>>input[name="url"]');
}
get token() {
return $('>>>input[name="token"]');
}
}
export default new ScimForm();

View file

@ -0,0 +1,22 @@
import Page from "../page.js";
import { $ } from "@wdio/globals";
export class TransparentProxyForm extends Page {
async setAuthorizationFlow(selector: string) {
await this.searchSelect(
'>>>ak-flow-search[name="authorizationFlow"] input[type="text"]',
"authorizationFlow",
`button*=${selector}`,
);
}
get externalHost() {
return $('>>>input[name="externalHost"]');
}
get internalHost() {
return $('>>>input[name="internalHost"]');
}
}
export default new TransparentProxyForm();

View file

@ -0,0 +1,167 @@
import ApplicationWizardView from "../pageobjects/application-wizard.page.js";
import ApplicationsListPage from "../pageobjects/applications-list.page.js";
import { randomId } from "../utils/index.js";
import { login } from "../utils/login.js";
import { expect } from "@wdio/globals";
async function reachTheProvider(title: string) {
const newPrefix = randomId();
await ApplicationsListPage.logout();
await login();
await ApplicationsListPage.open();
await expect(await ApplicationsListPage.pageHeader).toHaveText("Applications");
await ApplicationsListPage.startWizardButton.click();
await ApplicationWizardView.wizardTitle.waitForDisplayed();
await expect(await ApplicationWizardView.wizardTitle).toHaveText("New application");
await ApplicationWizardView.app.name.setValue(`${title} - ${newPrefix}`);
await ApplicationWizardView.app.uiSettings.scrollIntoView();
await ApplicationWizardView.app.uiSettings.click();
await ApplicationWizardView.app.launchUrl.scrollIntoView();
await ApplicationWizardView.app.launchUrl.setValue("http://example.goauthentik.io");
await ApplicationWizardView.nextButton.click();
return await ApplicationWizardView.pause();
}
async function getCommitMessage() {
await ApplicationWizardView.successMessage.waitForDisplayed();
return await ApplicationWizardView.successMessage;
}
const SUCCESS_MESSAGE = "Your application has been saved";
const EXPLICIT_CONSENT = "default-provider-authorization-explicit-consent";
describe("Configure Applications with the Application Wizard", () => {
it("Should configure a simple LDAP Application", async () => {
await reachTheProvider("New LDAP Application");
await ApplicationWizardView.providerList.waitForDisplayed();
await ApplicationWizardView.ldapProvider.scrollIntoView();
await ApplicationWizardView.ldapProvider.click();
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await ApplicationWizardView.ldap.setBindFlow("default-authentication-flow");
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await expect(getCommitMessage()).toHaveText(SUCCESS_MESSAGE);
});
it("Should configure a simple Oauth2 Application", async () => {
await reachTheProvider("New Oauth2 Application");
await ApplicationWizardView.providerList.waitForDisplayed();
await ApplicationWizardView.oauth2Provider.scrollIntoView();
await ApplicationWizardView.oauth2Provider.click();
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await ApplicationWizardView.oauth.setAuthorizationFlow(EXPLICIT_CONSENT);
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await expect(getCommitMessage()).toHaveText(SUCCESS_MESSAGE);
});
it("Should configure a simple SAML Application", async () => {
await reachTheProvider("New SAML Application");
await ApplicationWizardView.providerList.waitForDisplayed();
await ApplicationWizardView.samlProvider.scrollIntoView();
await ApplicationWizardView.samlProvider.click();
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await ApplicationWizardView.saml.setAuthorizationFlow(EXPLICIT_CONSENT);
await ApplicationWizardView.saml.acsUrl.setValue("http://example.com:8000/");
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await expect(getCommitMessage()).toHaveText(SUCCESS_MESSAGE);
});
it("Should configure a simple SCIM Application", async () => {
await reachTheProvider("New SCIM Application");
await ApplicationWizardView.providerList.waitForDisplayed();
await ApplicationWizardView.scimProvider.scrollIntoView();
await ApplicationWizardView.scimProvider.click();
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await ApplicationWizardView.scim.url.setValue("http://example.com:8000/");
await ApplicationWizardView.scim.token.setValue("a-very-basic-token");
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await expect(getCommitMessage()).toHaveText(SUCCESS_MESSAGE);
});
it("Should configure a simple Radius Application", async () => {
await reachTheProvider("New Radius Application");
await ApplicationWizardView.providerList.waitForDisplayed();
await ApplicationWizardView.radiusProvider.scrollIntoView();
await ApplicationWizardView.radiusProvider.click();
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await ApplicationWizardView.radius.setAuthenticationFlow("default-authentication-flow");
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await expect(getCommitMessage()).toHaveText(SUCCESS_MESSAGE);
});
it("Should configure a simple Transparent Proxy Application", async () => {
await reachTheProvider("New Transparent Proxy Application");
await ApplicationWizardView.providerList.waitForDisplayed();
await ApplicationWizardView.proxyProviderProxy.scrollIntoView();
await ApplicationWizardView.proxyProviderProxy.click();
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await ApplicationWizardView.transparentProxy.setAuthorizationFlow(EXPLICIT_CONSENT);
await ApplicationWizardView.transparentProxy.externalHost.setValue(
"http://external.example.com",
);
await ApplicationWizardView.transparentProxy.internalHost.setValue(
"http://internal.example.com",
);
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await expect(getCommitMessage()).toHaveText(SUCCESS_MESSAGE);
});
it("Should configure a simple Forward Proxy Application", async () => {
await reachTheProvider("New Forward Proxy Application");
await ApplicationWizardView.providerList.waitForDisplayed();
await ApplicationWizardView.proxyProviderForwardsingle.scrollIntoView();
await ApplicationWizardView.proxyProviderForwardsingle.click();
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await ApplicationWizardView.forwardProxy.setAuthorizationFlow(EXPLICIT_CONSENT);
await ApplicationWizardView.forwardProxy.externalHost.setValue(
"http://external.example.com",
);
await ApplicationWizardView.nextButton.click();
await ApplicationWizardView.pause();
await expect(getCommitMessage()).toHaveText(SUCCESS_MESSAGE);
});
});

View file

@ -86,7 +86,7 @@ export const config: Options.Testrunner = {
// Define all options that are relevant for the WebdriverIO instance here
//
// Level of logging verbosity: trace | debug | info | warn | error | silent
logLevel: "info",
logLevel: "warn",
//
// Set specific log levels per logger
// loggers:
@ -157,4 +157,149 @@ export const config: Options.Testrunner = {
ui: "bdd",
timeout: 60000,
},
//
// =====
// Hooks
// =====
// WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance
// it and to build services around it. You can either apply a single function or an array of
// methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got
// resolved to continue.
/**
* Gets executed once before all workers get launched.
* @param {object} config wdio configuration object
* @param {Array.<Object>} capabilities list of capabilities details
*/
// onPrepare: function (config, capabilities) {
// },
/**
* Gets executed before a worker process is spawned and can be used to initialise specific service
* for that worker as well as modify runtime environments in an async fashion.
* @param {string} cid capability id (e.g 0-0)
* @param {object} caps object containing capabilities for session that will be spawn in the worker
* @param {object} specs specs to be run in the worker process
* @param {object} args object that will be merged with the main configuration once worker is initialized
* @param {object} execArgv list of string arguments passed to the worker process
*/
// onWorkerStart: function (cid, caps, specs, args, execArgv) {
// },
/**
* Gets executed just after a worker process has exited.
* @param {string} cid capability id (e.g 0-0)
* @param {number} exitCode 0 - success, 1 - fail
* @param {object} specs specs to be run in the worker process
* @param {number} retries number of retries used
*/
// onWorkerEnd: function (cid, exitCode, specs, retries) {
// },
/**
* Gets executed just before initialising the webdriver session and test framework. It allows you
* to manipulate configurations depending on the capability or spec.
* @param {object} config wdio configuration object
* @param {Array.<Object>} capabilities list of capabilities details
* @param {Array.<String>} specs List of spec file paths that are to be run
* @param {string} cid worker id (e.g. 0-0)
*/
// beforeSession: function (config, capabilities, specs, cid) {
// },
/**
* Gets executed before test execution begins. At this point you can access to all global
* variables like `browser`. It is the perfect place to define custom commands.
* @param {Array.<Object>} capabilities list of capabilities details
* @param {Array.<String>} specs List of spec file paths that are to be run
* @param {object} browser instance of created browser/device session
*/
before: function (_capabilities, _specs) {},
/**
* Runs before a WebdriverIO command gets executed.
* @param {string} commandName hook command name
* @param {Array} args arguments that command would receive
*/
// beforeCommand: function (commandName, args) {
// },
/**
* Hook that gets executed before the suite starts
* @param {object} suite suite details
*/
// beforeSuite: function (suite) {
// },
/**
* Function to be executed before a test (in Mocha/Jasmine) starts.
*/
// beforeTest: function (test, context) {
// },
/**
* Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
* beforeEach in Mocha)
*/
// beforeHook: function (test, context) {
// },
/**
* Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling
* afterEach in Mocha)
*/
// afterHook: function (test, context, { error, result, duration, passed, retries }) {
// },
/**
* Function to be executed after a test (in Mocha/Jasmine only)
* @param {object} test test object
* @param {object} context scope object the test was executed with
* @param {Error} result.error error object in case the test fails, otherwise `undefined`
* @param {*} result.result return object of test function
* @param {number} result.duration duration of test
* @param {boolean} result.passed true if test has passed, otherwise false
* @param {object} result.retries information about spec related retries, e.g. `{ attempts: 0, limit: 0 }`
*/
// afterTest: function(test, context, { error, result, duration, passed, retries }) {
// },
/**
* Hook that gets executed after the suite has ended
* @param {object} suite suite details
*/
// afterSuite: function (suite) {
// },
/**
* Runs after a WebdriverIO command gets executed
* @param {string} commandName hook command name
* @param {Array} args arguments that command would receive
* @param {number} result 0 - command success, 1 - command error
* @param {object} error error object if any
*/
// afterCommand: function (commandName, args, result, error) {
// },
/**
* Gets executed after all tests are done. You still have access to all global variables from
* the test.
* @param {number} result 0 - test pass, 1 - test fail
* @param {Array.<Object>} capabilities list of capabilities details
* @param {Array.<String>} specs List of spec file paths that ran
*/
// after: function (result, capabilities, specs) {
// },
/**
* Gets executed right after terminating the webdriver session.
* @param {object} config wdio configuration object
* @param {Array.<Object>} capabilities list of capabilities details
* @param {Array.<String>} specs List of spec file paths that ran
*/
// afterSession: function (config, capabilities, specs) {
// },
/**
* Gets executed after all workers got shut down and the process is about to exit. An error
* thrown in the onComplete hook will result in the test run failing.
* @param {object} exitCode 0 - success, 1 - fail
* @param {object} config wdio configuration object
* @param {Array.<Object>} capabilities list of capabilities details
* @param {<Object>} results object containing test results
*/
// onComplete: function(exitCode, config, capabilities, results) {
// },
/**
* Gets executed when a refresh happens.
* @param {string} oldSessionId session ID of the old session
* @param {string} newSessionId session ID of the new session
*/
// onReload: function(oldSessionId, newSessionId) {
// }
};

View file

@ -1,7 +1,12 @@
import "@goauthentik/admin/applications/ProviderSelectModal";
import { iconHelperText } from "@goauthentik/admin/helperText";
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
import { first, groupBy } from "@goauthentik/common/utils";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-file-input";
import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";
import "@goauthentik/components/ak-textarea-input";
import { rootInterface } from "@goauthentik/elements/Base";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
@ -23,12 +28,34 @@ import {
CoreApi,
PolicyEngineMode,
Provider,
ProvidersAllListRequest,
ProvidersApi,
} from "@goauthentik/api";
import "./components/ak-backchannel-input";
import "./components/ak-provider-search-input";
export const policyOptions = [
{
label: "any",
value: PolicyEngineMode.Any,
default: true,
description: html`${msg("Any policy must match to grant access")}`,
},
{
label: "all",
value: PolicyEngineMode.All,
description: html`${msg("All policies must match to grant access")}`,
},
];
@customElement("ak-application-form")
export class ApplicationForm extends ModelForm<Application, string> {
constructor() {
super();
this.handleConfirmBackchannelProviders = this.handleConfirmBackchannelProviders.bind(this);
this.makeRemoveBackchannelProviderHandler =
this.makeRemoveBackchannelProviderHandler.bind(this);
}
async loadInstance(pk: string): Promise<Application> {
const app = await new CoreApi(DEFAULT_CONFIG).coreApplicationsRetrieve({
slug: pk,
@ -89,237 +116,137 @@ export class ApplicationForm extends ModelForm<Application, string> {
return app;
}
renderForm(): TemplateResult {
return html` <ak-form-element-horizontal label=${msg("Name")} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">${msg("Application's display Name.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Slug")} ?required=${true} name="slug">
<input
type="text"
value="${ifDefined(this.instance?.slug)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${msg("Internal application name, used in URLs.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Group")} name="group">
<input
type="text"
value="${ifDefined(this.instance?.group)}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${msg(
"Optionally enter a group name. Applications with identical groups are shown grouped together.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Provider")} name="provider">
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Provider[]> => {
const args: ProvidersAllListRequest = {
ordering: "name",
};
if (query !== undefined) {
args.search = query;
}
const items = await new ProvidersApi(DEFAULT_CONFIG).providersAllList(args);
return items.results;
}}
.renderElement=${(item: Provider): string => {
return item.name;
}}
.value=${(item: Provider | undefined): number | undefined => {
return item?.pk;
}}
.groupBy=${(items: Provider[]) => {
return groupBy(items, (item) => item.verboseName);
}}
.selected=${(item: Provider): boolean => {
return this.instance?.provider === item.pk;
}}
?blankable=${true}
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${msg("Select a provider that this application should use.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Backchannel providers")}
name="backchannelProviders"
>
<div class="pf-c-input-group">
<ak-provider-select-table
?backchannelOnly=${true}
.confirm=${(items: Provider[]) => {
handleConfirmBackchannelProviders({ items }: { items: Provider[] }) {
this.backchannelProviders = items;
this.requestUpdate();
return Promise.resolve();
}}
>
<button slot="trigger" class="pf-c-button pf-m-control" type="button">
<pf-tooltip position="top" content=${msg("Add provider")}>
<i class="fas fa-plus" aria-hidden="true"></i>
</pf-tooltip>
</button>
</ak-provider-select-table>
<div class="pf-c-form-control">
<ak-chip-group>
${this.backchannelProviders.map((provider) => {
return html`<ak-chip
.removable=${true}
value=${ifDefined(provider.pk)}
@remove=${() => {
}
makeRemoveBackchannelProviderHandler(provider: Provider) {
return () => {
const idx = this.backchannelProviders.indexOf(provider);
this.backchannelProviders.splice(idx, 1);
this.requestUpdate();
}}
>
${provider.name}
</ak-chip>`;
})}
</ak-chip-group>
</div>
</div>
<p class="pf-c-form__helper-text">
${msg(
};
}
handleClearIcon(ev: Event) {
ev.stopPropagation();
if (!(ev instanceof InputEvent) || !ev.target) {
return;
}
this.clearIcon = !!(ev.target as HTMLInputElement).checked;
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-text-input
name="name"
value=${this.instance?.name}
label=${msg("Name")}
required
help=${msg("Application's display Name.")}
></ak-text-input>
<ak-text-input
name="slug"
value=${this.instance?.slug}
label=${msg("Slug")}
required
help=${msg("Internal application name used in URLs.")}
></ak-text-input>
<ak-text-input
name="group"
value=${this.instance?.group}
label=${msg("Group")}
help=${msg(
"Optionally enter a group name. Applications with identical groups are shown grouped together.",
)}
></ak-text-input>
<ak-provider-search-input
name="provider"
label=${msg("Provider")}
value=${this.instance?.provider}
help=${msg("Select a provider that this application should use.")}
blankable
></ak-provider-search-input>
<ak-backchannel-providers-input
name="backchannelProviders"
label=${msg("Backchannel Providers")}
help=${msg(
"Select backchannel providers which augment the functionality of the main provider.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
.providers=${this.backchannelProviders}
.confirm=${this.handleConfirmBackchannelProviders}
.remover=${this.makeRemoveBackchannelProviderHandler}
.tooltip=${html`<pf-tooltip
position="top"
content=${msg("Add provider")}
></pf-tooltip>`}
>
</ak-backchannel-providers-input>
<ak-radio-input
label=${msg("Policy engine mode")}
?required=${true}
required
name="policyEngineMode"
>
<ak-radio
.options=${[
{
label: "any",
value: PolicyEngineMode.Any,
default: true,
description: html`${msg("Any policy must match to grant access")}`,
},
{
label: "all",
value: PolicyEngineMode.All,
description: html`${msg("All policies must match to grant access")}`,
},
]}
.options=${policyOptions}
.value=${this.instance?.policyEngineMode}
>
</ak-radio>
</ak-form-element-horizontal>
></ak-radio-input>
<ak-form-group>
<span slot="header"> ${msg("UI settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("Launch URL")} name="metaLaunchUrl">
<input
type="text"
value="${ifDefined(this.instance?.metaLaunchUrl)}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${msg(
<ak-text-input
name="metaLaunchUrl"
label=${msg("Launch URL")}
value=${ifDefined(this.instance?.metaLaunchUrl)}
help=${msg(
"If left empty, authentik will try to extract the launch URL based on the selected provider.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="openInNewTab">
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
></ak-text-input>
<ak-switch-input
name="openInNewTab"
?checked=${first(this.instance?.openInNewTab, false)}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"></i>
</span>
</span>
<span class="pf-c-switch__label">${msg("Open in new tab")}</span>
</label>
<p class="pf-c-form__helper-text">
${msg(
label=${msg("Open in new tab")}
help=${msg(
"If checked, the launch URL will open in a new browser tab or window from the user's application library.",
)}
</p>
</ak-form-element-horizontal>
>
</ak-switch-input>
${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanSaveMedia)
? html`<ak-form-element-horizontal label="${msg("Icon")}" name="metaIcon">
<input type="file" value="" class="pf-c-form-control" />
? html`<ak-file-input
label="${msg("Icon")}"
name="metaIcon"
value=${this.instance?.metaIcon}
current=${msg("Currently set to:")}
></ak-file-input>
${this.instance?.metaIcon
? html`
<p class="pf-c-form__helper-text">
${msg("Currently set to:")}
${this.instance?.metaIcon}
</p>
`
: html``}
</ak-form-element-horizontal>
${this.instance?.metaIcon
? html`
<ak-form-element-horizontal>
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
@change=${(ev: Event) => {
const target =
ev.target as HTMLInputElement;
this.clearIcon = target.checked;
}}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i
class="fas fa-check"
aria-hidden="true"
></i>
</span>
</span>
<span class="pf-c-switch__label">
${msg("Clear icon")}
</span>
</label>
<p class="pf-c-form__helper-text">
${msg("Delete currently set icon.")}
</p>
</ak-form-element-horizontal>
<ak-switch-input
name=""
label=${msg("Clear icon")}
help=${msg("Delete currently set icon.")}
@change=${this.handleClearIcon}
></ak-switch-input>
`
: html``}`
: html`<ak-form-element-horizontal label=${msg("Icon")} name="metaIcon">
<input
type="text"
value="${first(this.instance?.metaIcon, "")}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">${iconHelperText}</p>
</ak-form-element-horizontal>`}
<ak-form-element-horizontal label=${msg("Publisher")} name="metaPublisher">
<input
type="text"
value="${ifDefined(this.instance?.metaPublisher)}"
class="pf-c-form-control"
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Description")} name="metaDescription">
<textarea class="pf-c-form-control">
${ifDefined(this.instance?.metaDescription)}</textarea
: html` <ak-text-input
label=${msg("Icon")}
name="metaIcon"
value=${first(this.instance?.metaIcon, "")}
help=${iconHelperText}
>
</ak-form-element-horizontal>
</ak-text-input>`}
<ak-text-input
label=${msg("Publisher")}
name="metaPublisher"
value="${ifDefined(this.instance?.metaPublisher)}"
></ak-text-input>
<ak-textarea-input
label=${msg("Description")}
name="metaDescription"
value=${ifDefined(this.instance?.metaDescription)}
></ak-textarea-input>
</div>
</ak-form-group>`;
</ak-form-group>
</form>`;
}
}

View file

@ -1,5 +1,4 @@
import "@goauthentik/admin/applications/ApplicationForm";
import "@goauthentik/admin/applications/wizard/ApplicationWizard";
import { PFSize } from "@goauthentik/app/elements/Spinner";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { uiConfig } from "@goauthentik/common/ui/config";
@ -10,6 +9,7 @@ import "@goauthentik/elements/buttons/SpinnerButton";
import "@goauthentik/elements/forms/DeleteBulkForm";
import "@goauthentik/elements/forms/ModalForm";
import { getURLParam } from "@goauthentik/elements/router/RouteMatch";
// import { getURLParam } from "@goauthentik/elements/router/RouteMatch";
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
import { TableColumn } from "@goauthentik/elements/table/Table";
import { TablePage } from "@goauthentik/elements/table/TablePage";
@ -23,6 +23,8 @@ import PFCard from "@patternfly/patternfly/components/Card/card.css";
import { Application, CoreApi } from "@goauthentik/api";
import "./ApplicationWizardHint";
@customElement("ak-application-list")
export class ApplicationListPage extends TablePage<Application> {
searchEnabled(): boolean {
@ -33,7 +35,7 @@ export class ApplicationListPage extends TablePage<Application> {
}
pageDescription(): string {
return msg(
"External Applications which use authentik as Identity-Provider, utilizing protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.",
"External applications that use authentik as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.",
);
}
pageIcon(): string {
@ -87,14 +89,21 @@ export class ApplicationListPage extends TablePage<Application> {
];
}
renderSectionBefore(): TemplateResult {
return html`<ak-application-wizard-hint></ak-application-wizard-hint>`;
}
renderSidebarAfter(): TemplateResult {
// Rendering the wizard with .open here, as if we set the attribute in
// renderObjectCreate() it'll open two wizards, since that function gets called twice
return html`<ak-application-wizard
/* Re-enable the wizard later:
<ak-application-wizard
.open=${getURLParam("createWizard", false)}
.showButton=${false}
></ak-application-wizard>
<div class="pf-c-sidebar__panel pf-m-width-25">
></ak-application-wizard>*/
return html` <div class="pf-c-sidebar__panel pf-m-width-25">
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-markdown .md=${MDApplication}></ak-markdown>

View file

@ -1,4 +1,4 @@
import { MessageLevel } from "@goauthentik/common/messages";
import "@goauthentik/admin/applications/wizard/ak-application-wizard";
import {
ShowHintController,
ShowHintControllerHost,
@ -6,18 +6,37 @@ import {
import "@goauthentik/components/ak-hint/ak-hint";
import "@goauthentik/components/ak-hint/ak-hint-body";
import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/Label";
import "@goauthentik/elements/buttons/ActionButton/ak-action-button";
import { showMessage } from "@goauthentik/elements/messages/MessageContainer";
import { getURLParam } from "@goauthentik/elements/router/RouteMatch";
import { html, nothing } from "lit";
import { msg } from "@lit/localize";
import { html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { styleMap } from "lit/directives/style-map.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFLabel from "@patternfly/patternfly/components/Label/label.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
const closeButtonIcon = html`<svg
fill="currentColor"
height="1em"
width="1em"
viewBox="0 0 352 512"
aria-hidden="true"
role="img"
style="vertical-align: -0.125em;"
>
<path
d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"
></path>
</svg>`;
@customElement("ak-application-wizard-hint")
export class AkApplicationWizardHint extends AKElement implements ShowHintControllerHost {
static get styles() {
return [PFPage];
return [PFButton, PFPage, PFLabel];
}
@property({ type: Boolean, attribute: "show-hint" })
@ -36,33 +55,60 @@ export class AkApplicationWizardHint extends AKElement implements ShowHintContro
);
}
renderReminder() {
const sectionStyles = {
paddingBottom: "0",
marginBottom: "-0.5rem",
marginRight: "0.0625rem",
textAlign: "right",
};
const textStyle = { maxWidth: "60ch" };
return html`<section
class="pf-c-page__main-section pf-m-no-padding-mobile"
style="${styleMap(sectionStyles)}"
>
<span class="pf-c-label">
<a class="pf-c-label__content" @click=${this.showHintController.show}>
<span class="pf-c-label__text" style="${styleMap(textStyle)}">
${msg("One hint, 'New Application Wizard', is currently hidden")}
</span>
<button
aria-disabled="false"
aria-label="Restore Application Wizard Hint "
class="pf-c-button pf-m-plain"
type="button"
data-ouia-safe="true"
>
${closeButtonIcon}
</button>
</a>
</span>
</section>`;
}
renderHint() {
return html` <section class="pf-c-page__main-section pf-m-no-padding-mobile">
<ak-hint>
<ak-hint-body>
<p>
Authentik has a new Application Wizard that can configure both an
application and its authentication provider at the same time.
<a href="(link to docs)">Learn more about the wizard here.</a>
You can now configure both an application and its authentication provider at
the same time with our new Application Wizard.
<!-- <a href="(link to docs)">Learn more about the wizard here.</a> -->
</p>
<ak-action-button
class="pf-m-secondary"
.apiRequest=${() => {
showMessage({
message: "This would have shown the wizard",
level: MessageLevel.success,
});
}}
>Create with Wizard</ak-action-button
></ak-hint-body
>
<ak-application-wizard
.open=${getURLParam("createWizard", false)}
.showButton=${false}
></ak-application-wizard>
</ak-hint-body>
${this.showHintController.render()}
</ak-hint>
</section>`;
}
render() {
return this.showHint || this.forceHint ? this.renderHint() : nothing;
return this.showHint || this.forceHint ? this.renderHint() : this.renderReminder();
}
}

View file

@ -0,0 +1,80 @@
import "@goauthentik/admin/applications/ProviderSelectModal";
import { AKElement } from "@goauthentik/elements/Base";
import { TemplateResult, html, nothing } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { map } from "lit/directives/map.js";
import { Provider } from "@goauthentik/api";
@customElement("ak-backchannel-providers-input")
export class AkBackchannelProvidersInput extends AKElement {
// Render into the lightDOM. This effectively erases the shadowDOM nature of this component, but
// we're not actually using that and, for the meantime, we need the form handlers to be able to
// find the children of this component.
//
// This field is so highly specialized that it would make more sense if we put the API and the
// fetcher here.
//
// TODO: This abstraction is wrong; it's putting *more* layers in as a way of managing the
// visual clutter and legibility issues of ak-form-elemental-horizontal and patternfly in
// general.
protected createRenderRoot() {
return this;
}
@property({ type: String })
name!: string;
@property({ type: String })
label = "";
@property({ type: Array })
providers: Provider[] = [];
@property({ type: Object })
tooltip?: TemplateResult;
@property({ attribute: false, type: Object })
confirm!: ({ items }: { items: Provider[] }) => Promise<void>;
@property({ attribute: false, type: Object })
remover!: (provider: Provider) => () => void;
@property({ type: String })
value = "";
@property({ type: Boolean })
required = false;
@property({ type: String })
help = "";
render() {
const renderOneChip = (provider: Provider) =>
html`<ak-chip
.removable=${true}
value=${ifDefined(provider.pk)}
@remove=${this.remover(provider)}
>${provider.name}</ak-chip
>`;
return html`
<ak-form-element-horizontal label=${this.label} name=${name}>
<div class="pf-c-input-group">
<ak-provider-select-table ?backchannelOnly=${true} .confirm=${confirm}>
<button slot="trigger" class="pf-c-button pf-m-control" type="button">
$ {this.tooltip ? this.tooltip : nothing }
<i class="fas fa-plus" aria-hidden="true"></i>
</button>
</ak-provider-select-table>
<div class="pf-c-form-control">
<ak-chip-group> ${map(this.providers, renderOneChip)} </ak-chip-group>
</div>
</div>
${this.help ? html`<p class="pf-c-form__helper-radio">${this.help}</p>` : nothing}
</ak-form-element-horizontal>
`;
}
}

View file

@ -0,0 +1,80 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { groupBy } from "@goauthentik/common/utils";
import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/forms/SearchSelect";
import { html, nothing } from "lit";
import { customElement, property } from "lit/decorators.js";
import { Provider, ProvidersAllListRequest, ProvidersApi } from "@goauthentik/api";
const renderElement = (item: Provider) => item.name;
const renderValue = (item: Provider | undefined) => item?.pk;
const doGroupBy = (items: Provider[]) => groupBy(items, (item) => item.verboseName);
async function fetch(query?: string) {
const args: ProvidersAllListRequest = {
ordering: "name",
};
if (query !== undefined) {
args.search = query;
}
const items = await new ProvidersApi(DEFAULT_CONFIG).providersAllList(args);
return items.results;
}
@customElement("ak-provider-search-input")
export class AkProviderInput extends AKElement {
// Render into the lightDOM. This effectively erases the shadowDOM nature of this component, but
// we're not actually using that and, for the meantime, we need the form handlers to be able to
// find the children of this component.
//
// TODO: This abstraction is wrong; it's putting *more* layers in as a way of managing the
// visual clutter and legibility issues of ak-form-elemental-horizontal and patternfly in
// general.
protected createRenderRoot() {
return this;
}
@property({ type: String })
name!: string;
@property({ type: String })
label = "";
@property({ type: Number })
value?: number;
@property({ type: Boolean })
required = false;
@property({ type: Boolean })
blankable = false;
@property({ type: String })
help = "";
constructor() {
super();
this.selected = this.selected.bind(this);
}
selected(item: Provider) {
return this.value !== undefined && this.value === item.pk;
}
render() {
return html` <ak-form-element-horizontal label=${this.label} name=${this.name}>
<ak-search-select
.selected=${this.selected}
.fetchObjects=${fetch}
.renderElement=${renderElement}
.value=${renderValue}
.groupBy=${doGroupBy}
?blankable=${this.blankable}
>
</ak-search-select>
${this.help ? html`<p class="pf-c-form__helper-text">${this.help}</p>` : nothing}
</ak-form-element-horizontal>`;
}
}

View file

@ -1,64 +0,0 @@
import "@goauthentik/admin/applications/wizard/InitialApplicationWizardPage";
import "@goauthentik/admin/applications/wizard/TypeApplicationWizardPage";
import "@goauthentik/admin/applications/wizard/ldap/TypeLDAPApplicationWizardPage";
import "@goauthentik/admin/applications/wizard/link/TypeLinkApplicationWizardPage";
import "@goauthentik/admin/applications/wizard/oauth/TypeOAuthAPIApplicationWizardPage";
import "@goauthentik/admin/applications/wizard/oauth/TypeOAuthApplicationWizardPage";
import "@goauthentik/admin/applications/wizard/oauth/TypeOAuthCodeApplicationWizardPage";
import "@goauthentik/admin/applications/wizard/oauth/TypeOAuthImplicitApplicationWizardPage";
import "@goauthentik/admin/applications/wizard/proxy/TypeProxyApplicationWizardPage";
import "@goauthentik/admin/applications/wizard/saml/TypeSAMLApplicationWizardPage";
import "@goauthentik/admin/applications/wizard/saml/TypeSAMLConfigApplicationWizardPage";
import "@goauthentik/admin/applications/wizard/saml/TypeSAMLImportApplicationWizardPage";
import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/wizard/Wizard";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { CSSResult, TemplateResult, html } from "lit";
import { property } from "lit/decorators.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
@customElement("ak-application-wizard")
export class ApplicationWizard extends AKElement {
static get styles(): CSSResult[] {
return [PFBase, PFButton, PFRadio];
}
@property({ type: Boolean })
open = false;
@property()
createText = msg("Create");
@property({ type: Boolean })
showButton = true;
@property({ attribute: false })
finalHandler: () => Promise<void> = () => {
return Promise.resolve();
};
render(): TemplateResult {
return html`
<ak-wizard
.open=${this.open}
.steps=${["ak-application-wizard-initial", "ak-application-wizard-type"]}
header=${msg("New application")}
description=${msg("Create a new application.")}
.finalHandler=${() => {
return this.finalHandler();
}}
>
${this.showButton
? html`<button slot="trigger" class="pf-c-button pf-m-primary">
${this.createText}
</button>`
: html``}
</ak-wizard>
`;
}
}

View file

@ -0,0 +1,28 @@
import { css } from "lit";
import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFCard from "@patternfly/patternfly/components/Card/card.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
import PFInputGroup from "@patternfly/patternfly/components/InputGroup/input-group.css";
import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";
import PFSwitch from "@patternfly/patternfly/components/Switch/switch.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
export const styles = [
PFBase,
PFCard,
PFButton,
PFForm,
PFAlert,
PFRadio,
PFInputGroup,
PFFormControl,
PFSwitch,
css`
select[multiple] {
height: 15em;
}
`,
];

View file

@ -0,0 +1,35 @@
import { WizardPanel } from "@goauthentik/components/ak-wizard-main/types";
import { AKElement } from "@goauthentik/elements/Base";
import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter";
import { consume } from "@lit-labs/context";
import { query } from "@lit/reactive-element/decorators.js";
import { styles as AwadStyles } from "./BasePanel.css";
import { applicationWizardContext } from "./ContextIdentity";
import type { ApplicationWizardState, ApplicationWizardStateUpdate } from "./types";
export class ApplicationWizardPageBase
extends CustomEmitterElement(AKElement)
implements WizardPanel
{
static get styles() {
return AwadStyles;
}
@query("form")
form!: HTMLFormElement;
rendered = false;
@consume({ context: applicationWizardContext })
public wizard!: ApplicationWizardState;
// This used to be more complex; now it just establishes the event name.
dispatchWizardUpdate(update: ApplicationWizardStateUpdate) {
this.dispatchCustomEvent("ak-wizard-update", update);
}
}
export default ApplicationWizardPageBase;

View file

@ -0,0 +1,9 @@
import { createContext } from "@lit-labs/context";
import { ApplicationWizardState } from "./types";
export const applicationWizardContext = createContext<ApplicationWizardState>(
Symbol("ak-application-wizard-state-context"),
);
export default applicationWizardContext;

View file

@ -1,75 +0,0 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { convertToSlug } from "@goauthentik/common/utils";
import { KeyUnknown } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { WizardFormPage } from "@goauthentik/elements/wizard/WizardFormPage";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { TemplateResult, html } from "lit";
import { ApplicationRequest, CoreApi, Provider } from "@goauthentik/api";
@customElement("ak-application-wizard-initial")
export class InitialApplicationWizardPage extends WizardFormPage {
sidebarLabel = () => msg("Application details");
nextDataCallback = async (data: KeyUnknown): Promise<boolean> => {
const name = data.name as string;
let slug = convertToSlug(name || "");
// Check if an application with the generated slug already exists
const apps = await new CoreApi(DEFAULT_CONFIG).coreApplicationsList({
search: slug,
});
if (apps.results.filter((app) => app.slug == slug)) {
slug += "-1";
}
this.host.state["slug"] = slug;
this.host.state["name"] = name;
this.host.addActionBefore(msg("Create application"), async (): Promise<boolean> => {
const req: ApplicationRequest = {
name: name || "",
slug: slug,
metaPublisher: data.metaPublisher as string,
metaDescription: data.metaDescription as string,
};
if ("provider" in this.host.state) {
req.provider = (this.host.state["provider"] as Provider).pk;
}
if ("link" in this.host.state) {
req.metaLaunchUrl = this.host.state["link"] as string;
}
this.host.state["app"] = await new CoreApi(DEFAULT_CONFIG).coreApplicationsCreate({
applicationRequest: req,
});
return true;
});
return true;
};
renderForm(): TemplateResult {
return html`
<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${msg("Name")} ?required=${true} name="name">
<input type="text" value="" class="pf-c-form-control" required />
<p class="pf-c-form__helper-text">${msg("Application's display Name.")}</p>
</ak-form-element-horizontal>
<ak-form-group ?expanded=${true}>
<span slot="header"> ${msg("Additional UI settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Description")}
name="metaDescription"
>
<textarea class="pf-c-form-control"></textarea>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Publisher")} name="metaPublisher">
<input type="text" value="" class="pf-c-form-control" />
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>
`;
}
}

View file

@ -1,85 +0,0 @@
import { WizardPage } from "@goauthentik/elements/wizard/WizardPage";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { CSSResult, TemplateResult, html } from "lit";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { TypeCreate } from "@goauthentik/api";
@customElement("ak-application-wizard-type")
export class TypeApplicationWizardPage extends WizardPage {
applicationTypes: TypeCreate[] = [
{
component: "ak-application-wizard-type-oauth",
name: msg("OAuth2/OIDC"),
description: msg("Modern applications, APIs and Single-page applications."),
modelName: "",
},
{
component: "ak-application-wizard-type-saml",
name: msg("SAML"),
description: msg(
"XML-based SSO standard. Use this if your application only supports SAML.",
),
modelName: "",
},
{
component: "ak-application-wizard-type-proxy",
name: msg("Proxy"),
description: msg("Legacy applications which don't natively support SSO."),
modelName: "",
},
{
component: "ak-application-wizard-type-ldap",
name: msg("LDAP"),
description: msg(
"Provide an LDAP interface for applications and users to authenticate against.",
),
modelName: "",
},
{
component: "ak-application-wizard-type-link",
name: msg("Link"),
description: msg(
"Provide an LDAP interface for applications and users to authenticate against.",
),
modelName: "",
},
];
sidebarLabel = () => msg("Authentication method");
static get styles(): CSSResult[] {
return [PFBase, PFButton, PFForm, PFRadio];
}
render(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
${this.applicationTypes.map((type) => {
return html`<div class="pf-c-radio">
<input
class="pf-c-radio__input"
type="radio"
name="type"
id=${type.component}
@change=${() => {
this.host.steps = [
"ak-application-wizard-initial",
"ak-application-wizard-type",
type.component,
];
this.host.isValid = true;
}}
/>
<label class="pf-c-radio__label" for=${type.component}>${type.name}</label>
<span class="pf-c-radio__description">${type.description}</span>
</div>`;
})}
</form>`;
}
}

View file

@ -0,0 +1,124 @@
import { merge } from "@goauthentik/common/merge";
import { AkWizard } from "@goauthentik/components/ak-wizard-main/AkWizard";
import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter";
import { ContextProvider } from "@lit-labs/context";
import { msg } from "@lit/localize";
import { customElement, state } from "lit/decorators.js";
import applicationWizardContext from "./ContextIdentity";
import { newSteps } from "./steps";
import {
ApplicationStep,
ApplicationWizardState,
ApplicationWizardStateUpdate,
OneOfProvider,
} from "./types";
const freshWizardState = () => ({
providerModel: "",
app: {},
provider: {},
});
@customElement("ak-application-wizard")
export class ApplicationWizard extends CustomListenerElement(
AkWizard<ApplicationWizardStateUpdate, ApplicationStep>,
) {
constructor() {
super(msg("Create With Wizard"), msg("New application"), msg("Create a new application"));
this.steps = newSteps();
}
/**
* We're going to be managing the content of the forms by percolating all of the data up to this
* class, which will ultimately transmit all of it to the server as a transaction. The
* WizardFramework doesn't know anything about the nature of the data itself; it just forwards
* valid updates to us. So here we maintain a state object *and* update it so all child
* components can access the wizard state.
*
*/
@state()
wizardState: ApplicationWizardState = freshWizardState();
wizardStateProvider = new ContextProvider(this, {
context: applicationWizardContext,
initialValue: this.wizardState,
});
/**
* One of our steps has multiple display variants, one for each type of service provider. We
* want to *preserve* a customer's decisions about different providers; never make someone "go
* back and type it all back in," even if it's probably rare that someone will chose one
* provider, realize it's the wrong one, and go back to chose a different one, *and then go
* back*. Nonetheless, strive to *never* lose customer input.
*
*/
providerCache: Map<string, OneOfProvider> = new Map();
maybeProviderSwap(providerModel: string | undefined): boolean {
if (
providerModel === undefined ||
typeof providerModel !== "string" ||
providerModel === this.wizardState.providerModel
) {
return false;
}
this.providerCache.set(this.wizardState.providerModel, this.wizardState.provider);
const prevProvider = this.providerCache.get(providerModel);
this.wizardState.provider = prevProvider ?? {
name: `Provider for ${this.wizardState.app.name}`,
};
const method = this.steps.find(({ id }) => id === "provider-details");
if (!method) {
throw new Error("Could not find Authentication Method page?");
}
method.disabled = false;
return true;
}
// And this is where all the special cases go...
handleUpdate(detail: ApplicationWizardStateUpdate) {
if (detail.status === "submitted") {
this.step.valid = true;
this.requestUpdate();
return;
}
this.step.valid = this.step.valid || detail.status === "valid";
const update = detail.update;
if (!update) {
return;
}
if (this.maybeProviderSwap(update.providerModel)) {
this.requestUpdate();
}
this.wizardState = merge(this.wizardState, update) as ApplicationWizardState;
this.wizardStateProvider.setValue(this.wizardState);
this.requestUpdate();
}
close() {
this.steps = newSteps();
this.currentStep = 0;
this.wizardState = freshWizardState();
this.providerCache = new Map();
this.wizardStateProvider.setValue(this.wizardState);
this.frame.value!.open = false;
}
handleNav(stepId: number | undefined) {
if (stepId === undefined || this.steps[stepId] === undefined) {
throw new Error(`Attempt to navigate to undefined step: ${stepId}`);
}
if (stepId > this.currentStep && !this.step.valid) {
return;
}
this.currentStep = stepId;
this.requestUpdate();
}
}

View file

@ -0,0 +1,101 @@
import { policyOptions } from "@goauthentik/admin/applications/ApplicationForm";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-slug-input";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { TemplateResult, html } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import BasePanel from "../BasePanel";
@customElement("ak-application-wizard-application-details")
export class ApplicationWizardApplicationDetails extends BasePanel {
handleChange(ev: Event) {
if (!ev.target) {
console.warn(`Received event with no target: ${ev}`);
return;
}
const target = ev.target as HTMLInputElement;
const value = target.type === "checkbox" ? target.checked : target.value;
this.dispatchWizardUpdate({
update: {
app: {
[target.name]: value,
},
},
status: this.form.checkValidity() ? "valid" : "invalid",
});
}
validator() {
return this.form.reportValidity();
}
render(): TemplateResult {
return html` <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
<ak-text-input
name="name"
value=${ifDefined(this.wizard.app?.name)}
label=${msg("Name")}
required
help=${msg("Application's display Name.")}
id="ak-application-wizard-details-name"
></ak-text-input>
<ak-slug-input
name="slug"
value=${ifDefined(this.wizard.app?.slug)}
label=${msg("Slug")}
source="#ak-application-wizard-details-name"
required
help=${msg("Internal application name used in URLs.")}
></ak-slug-input>
<ak-text-input
name="group"
value=${ifDefined(this.wizard.app?.group)}
label=${msg("Group")}
help=${msg(
"Optionally enter a group name. Applications with identical groups are shown grouped together.",
)}
></ak-text-input>
<ak-radio-input
label=${msg("Policy engine mode")}
required
name="policyEngineMode"
.options=${policyOptions}
.value=${this.wizard.app?.policyEngineMode}
></ak-radio-input>
<ak-form-group aria-label="UI Settings">
<span slot="header"> ${msg("UI Settings")} </span>
<div slot="body" class="pf-c-form">
<ak-text-input
name="metaLaunchUrl"
label=${msg("Launch URL")}
value=${ifDefined(this.wizard.app?.metaLaunchUrl)}
help=${msg(
"If left empty, authentik will try to extract the launch URL based on the selected provider.",
)}
></ak-text-input>
<ak-switch-input
name="openInNewTab"
?checked=${first(this.wizard.app?.openInNewTab, false)}
label=${msg("Open in new tab")}
help=${msg(
"If checked, the launch URL will open in a new browser tab or window from the user's application library.",
)}
>
</ak-switch-input>
</div>
</ak-form-group>
</form>`;
}
}
export default ApplicationWizardApplicationDetails;

View file

@ -0,0 +1,161 @@
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import type { ProviderModelEnum as ProviderModelEnumType, TypeCreate } from "@goauthentik/api";
import { ProviderModelEnum, ProxyMode } from "@goauthentik/api";
import type {
LDAPProviderRequest,
ModelRequest,
OAuth2ProviderRequest,
ProxyProviderRequest,
RadiusProviderRequest,
SAMLProviderRequest,
SCIMProviderRequest,
} from "@goauthentik/api";
import { OneOfProvider } from "../types";
type ProviderRenderer = () => TemplateResult;
type ModelConverter = (provider: OneOfProvider) => ModelRequest;
type ProviderType = [
string,
string,
string,
ProviderRenderer,
ProviderModelEnumType,
ModelConverter,
];
export type LocalTypeCreate = TypeCreate & {
formName: string;
modelName: ProviderModelEnumType;
converter: ModelConverter;
};
// prettier-ignore
const _providerModelsTable: ProviderType[] = [
[
"oauth2provider",
msg("OAuth2/OpenID"),
msg("Modern applications, APIs and Single-page applications."),
() => html`<ak-application-wizard-authentication-by-oauth></ak-application-wizard-authentication-by-oauth>`,
ProviderModelEnum.Oauth2Oauth2provider,
(provider: OneOfProvider) => ({
providerModel: ProviderModelEnum.Oauth2Oauth2provider,
...(provider as OAuth2ProviderRequest),
}),
],
[
"ldapprovider",
msg("LDAP"),
msg("Provide an LDAP interface for applications and users to authenticate against."),
() => html`<ak-application-wizard-authentication-by-ldap></ak-application-wizard-authentication-by-ldap>`,
ProviderModelEnum.LdapLdapprovider,
(provider: OneOfProvider) => ({
providerModel: ProviderModelEnum.LdapLdapprovider,
...(provider as LDAPProviderRequest),
}),
],
[
"proxyprovider-proxy",
msg("Transparent Reverse Proxy"),
msg("For transparent reverse proxies with required authentication"),
() => html`<ak-application-wizard-authentication-for-reverse-proxy></ak-application-wizard-authentication-for-reverse-proxy>`,
ProviderModelEnum.ProxyProxyprovider,
(provider: OneOfProvider) => ({
providerModel: ProviderModelEnum.ProxyProxyprovider,
...(provider as ProxyProviderRequest),
mode: ProxyMode.Proxy,
}),
],
[
"proxyprovider-forwardsingle",
msg("Forward Auth Single Application"),
msg("For nginx's auth_request or traefix's forwardAuth"),
() => html`<ak-application-wizard-authentication-for-single-forward-proxy></ak-application-wizard-authentication-for-single-forward-proxy>`,
ProviderModelEnum.ProxyProxyprovider ,
(provider: OneOfProvider) => ({
providerModel: ProviderModelEnum.ProxyProxyprovider,
...(provider as ProxyProviderRequest),
mode: ProxyMode.ForwardSingle,
}),
],
[
"proxyprovider-forwarddomain",
msg("Forward Auth Domain Level"),
msg("For nginx's auth_request or traefix's forwardAuth per root domain"),
() => html`<ak-application-wizard-authentication-for-forward-proxy-domain></ak-application-wizard-authentication-for-forward-proxy-domain>`,
ProviderModelEnum.ProxyProxyprovider ,
(provider: OneOfProvider) => ({
providerModel: ProviderModelEnum.ProxyProxyprovider,
...(provider as ProxyProviderRequest),
mode: ProxyMode.ForwardDomain,
}),
],
[
"samlprovider",
msg("SAML Configuration"),
msg("Configure SAML provider manually"),
() => html`<ak-application-wizard-authentication-by-saml-configuration></ak-application-wizard-authentication-by-saml-configuration>`,
ProviderModelEnum.SamlSamlprovider,
(provider: OneOfProvider) => ({
providerModel: ProviderModelEnum.SamlSamlprovider,
...(provider as SAMLProviderRequest),
}),
],
[
"radiusprovider",
msg("RADIUS Configuration"),
msg("Configure RADIUS provider manually"),
() => html`<ak-application-wizard-authentication-by-radius></ak-application-wizard-authentication-by-radius>`,
ProviderModelEnum.RadiusRadiusprovider,
(provider: OneOfProvider) => ({
providerModel: ProviderModelEnum.RadiusRadiusprovider,
...(provider as RadiusProviderRequest),
}),
],
[
"scimprovider",
msg("SCIM configuration"),
msg("Configure SCIM provider manually"),
() => html`<ak-application-wizard-authentication-by-scim></ak-application-wizard-authentication-by-scim>`,
ProviderModelEnum.ScimScimprovider,
(provider: OneOfProvider) => ({
providerModel: ProviderModelEnum.ScimScimprovider,
...(provider as SCIMProviderRequest),
}),
],
];
function mapProviders([
formName,
name,
description,
_,
modelName,
converter,
]: ProviderType): LocalTypeCreate {
return {
formName,
name,
description,
component: "",
modelName,
converter,
};
}
export const providerModelsList = _providerModelsTable.map(mapProviders);
export const providerRendererList = new Map<string, ProviderRenderer>(
_providerModelsTable.map(([modelName, _0, _1, renderer]) => [modelName, renderer]),
);
export default providerModelsList;

View file

@ -0,0 +1,68 @@
import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { html } from "lit";
import { map } from "lit/directives/map.js";
import BasePanel from "../BasePanel";
import providerModelsList from "./ak-application-wizard-authentication-method-choice.choices";
import type { LocalTypeCreate } from "./ak-application-wizard-authentication-method-choice.choices";
@customElement("ak-application-wizard-authentication-method-choice")
export class ApplicationWizardAuthenticationMethodChoice extends BasePanel {
constructor() {
super();
this.handleChoice = this.handleChoice.bind(this);
this.renderProvider = this.renderProvider.bind(this);
}
handleChoice(ev: InputEvent) {
const target = ev.target as HTMLInputElement;
this.dispatchWizardUpdate({
update: { providerModel: target.value },
status: this.validator() ? "valid" : "invalid",
});
}
validator() {
const radios = Array.from(this.form.querySelectorAll('input[type="radio"]'));
const chosen = radios.find(
(radio: Element) => radio instanceof HTMLInputElement && radio.checked,
);
return !!chosen;
}
renderProvider(type: LocalTypeCreate) {
const method = this.wizard.providerModel;
return html`<div class="pf-c-radio">
<input
class="pf-c-radio__input"
type="radio"
name="type"
id="provider-${type.formName}"
value=${type.formName}
?checked=${type.formName === method}
@change=${this.handleChoice}
/>
<label class="pf-c-radio__label" for="provider-${type.formName}">${type.name}</label>
<span class="pf-c-radio__description">${type.description}</span>
</div>`;
}
render() {
return providerModelsList.length > 0
? html`<form class="pf-c-form pf-m-horizontal">
${map(providerModelsList, this.renderProvider)}
</form>`
: html`<ak-empty-state loading header=${msg("Loading")}></ak-empty-state>`;
}
}
export default ApplicationWizardAuthenticationMethodChoice;

View file

@ -0,0 +1,202 @@
import { EVENT_REFRESH } from "@goauthentik/app/common/constants";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { customElement, state } from "@lit/reactive-element/decorators.js";
import { TemplateResult, css, html, nothing } from "lit";
import { classMap } from "lit/directives/class-map.js";
import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css";
import PFProgressStepper from "@patternfly/patternfly/components/ProgressStepper/progress-stepper.css";
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
import PFBullseye from "@patternfly/patternfly/layouts/Bullseye/bullseye.css";
import {
ApplicationRequest,
CoreApi,
TransactionApplicationRequest,
TransactionApplicationResponse,
} from "@goauthentik/api";
import type { ModelRequest } from "@goauthentik/api";
import BasePanel from "../BasePanel";
import providerModelsList from "../auth-method-choice/ak-application-wizard-authentication-method-choice.choices";
function cleanApplication(app: Partial<ApplicationRequest>): ApplicationRequest {
return {
name: "",
slug: "",
...app,
};
}
type ProviderModelType = Exclude<ModelRequest["providerModel"], "11184809">;
type State = {
state: "idle" | "running" | "error" | "success";
label: string | TemplateResult;
icon: string[];
};
const idleState: State = {
state: "idle",
label: "",
icon: ["fa-cogs", "pf-m-pending"],
};
const runningState: State = {
state: "running",
label: msg("Saving Application..."),
icon: ["fa-cogs", "pf-m-info"],
};
const errorState: State = {
state: "error",
label: msg("Authentik was unable to save this application:"),
icon: ["fa-times-circle", "pf-m-danger"],
};
const successState: State = {
state: "success",
label: msg("Your application has been saved"),
icon: ["fa-check-circle", "pf-m-success"],
};
@customElement("ak-application-wizard-commit-application")
export class ApplicationWizardCommitApplication extends BasePanel {
static get styles() {
return [
...super.styles,
PFBullseye,
PFEmptyState,
PFTitle,
PFProgressStepper,
css`
.pf-c-title {
padding-bottom: var(--pf-global--spacer--md);
}
`,
];
}
@state()
commitState: State = idleState;
@state()
errors: string[] = [];
response?: TransactionApplicationResponse;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
willUpdate(_changedProperties: Map<string, any>) {
if (this.commitState === idleState) {
this.response = undefined;
this.commitState = runningState;
const providerModel = providerModelsList.find(
({ formName }) => formName === this.wizard.providerModel,
);
if (!providerModel) {
throw new Error(
`Could not determine provider model from user request: ${JSON.stringify(
this.wizard,
null,
2,
)}`,
);
}
const request: TransactionApplicationRequest = {
providerModel: providerModel.modelName as ProviderModelType,
app: cleanApplication(this.wizard.app),
provider: providerModel.converter(this.wizard.provider),
};
this.send(request);
return;
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
decodeErrors(body: Record<string, any>) {
const spaceify = (src: Record<string, string>) =>
Object.values(src).map((msg) => `\u00a0\u00a0\u00a0\u00a0${msg}`);
let errs: string[] = [];
if (body["app"] !== undefined) {
errs = [...errs, msg("In the Application:"), ...spaceify(body["app"])];
}
if (body["provider"] !== undefined) {
errs = [...errs, msg("In the Provider:"), ...spaceify(body["provider"])];
}
console.log(body, errs);
return errs;
}
async send(
data: TransactionApplicationRequest,
): Promise<TransactionApplicationResponse | void> {
this.errors = [];
new CoreApi(DEFAULT_CONFIG)
.coreTransactionalApplicationsUpdate({
transactionApplicationRequest: data,
})
.then((response: TransactionApplicationResponse) => {
this.response = response;
this.dispatchCustomEvent(EVENT_REFRESH);
this.dispatchWizardUpdate({ status: "submitted" });
this.commitState = successState;
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.catch((resolution: any) => {
resolution.response.json().then(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(body: Record<string, any>) => {
this.errors = this.decodeErrors(body);
},
);
this.commitState = errorState;
});
}
render(): TemplateResult {
const icon = classMap(
this.commitState.icon.reduce((acc, icon) => ({ ...acc, [icon]: true }), {}),
);
return html`
<div>
<div class="pf-l-bullseye">
<div class="pf-c-empty-state pf-m-lg">
<div class="pf-c-empty-state__content">
<i
class="fas fa- ${icon} pf-c-empty-state__icon"
aria-hidden="true"
></i>
<h1
data-commit-state=${this.commitState.state}
class="pf-c-title pf-m-lg"
>
${this.commitState.label}
</h1>
${this.errors.length > 0
? html`<ul>
${this.errors.map(
(msg) => html`<li><code>${msg}</code></li>`,
)}
</ul>`
: nothing}
</div>
</div>
</div>
</div>
`;
}
}
export default ApplicationWizardCommitApplication;

View file

@ -1,73 +0,0 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { KeyUnknown } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { WizardFormPage } from "@goauthentik/elements/wizard/WizardFormPage";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { TemplateResult, html } from "lit";
import {
CoreApi,
FlowDesignationEnum,
FlowsApi,
LDAPProviderRequest,
ProvidersApi,
UserServiceAccountResponse,
} from "@goauthentik/api";
@customElement("ak-application-wizard-type-ldap")
export class TypeLDAPApplicationWizardPage extends WizardFormPage {
sidebarLabel = () => msg("LDAP details");
nextDataCallback = async (data: KeyUnknown): Promise<boolean> => {
let name = this.host.state["name"] as string;
// Check if a provider with the name already exists
const providers = await new ProvidersApi(DEFAULT_CONFIG).providersAllList({
search: name,
});
if (providers.results.filter((provider) => provider.name == name)) {
name += "-1";
}
this.host.addActionBefore(msg("Create service account"), async (): Promise<boolean> => {
const serviceAccount = await new CoreApi(DEFAULT_CONFIG).coreUsersServiceAccountCreate({
userServiceAccountRequest: {
name: name,
createGroup: true,
},
});
this.host.state["serviceAccount"] = serviceAccount;
return true;
});
this.host.addActionBefore(msg("Create provider"), async (): Promise<boolean> => {
// Get all flows and default to the implicit authorization
const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
designation: FlowDesignationEnum.Authorization,
ordering: "slug",
});
const serviceAccount = this.host.state["serviceAccount"] as UserServiceAccountResponse;
const req: LDAPProviderRequest = {
name: name,
authorizationFlow: flows.results[0].pk,
baseDn: data.baseDN as string,
searchGroup: serviceAccount.groupPk,
};
const provider = await new ProvidersApi(DEFAULT_CONFIG).providersLdapCreate({
lDAPProviderRequest: req,
});
this.host.state["provider"] = provider;
return true;
});
return true;
};
renderForm(): TemplateResult {
const domainParts = window.location.hostname.split(".");
const defaultBaseDN = domainParts.map((part) => `dc=${part}`).join(",");
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${msg("Base DN")} name="baseDN" ?required=${true}>
<input type="text" value="${defaultBaseDN}" class="pf-c-form-control" required />
</ak-form-element-horizontal>
</form> `;
}
}

View file

@ -1,30 +0,0 @@
import { KeyUnknown } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { WizardFormPage } from "@goauthentik/elements/wizard/WizardFormPage";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { TemplateResult, html } from "lit";
@customElement("ak-application-wizard-type-link")
export class TypeLinkApplicationWizardPage extends WizardFormPage {
sidebarLabel = () => msg("Application Link");
nextDataCallback = async (data: KeyUnknown): Promise<boolean> => {
this.host.state["link"] = data.link;
return true;
};
renderForm(): TemplateResult {
return html`
<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${msg("Link")} ?required=${true} name="link">
<input type="text" value="" class="pf-c-form-control" required />
<p class="pf-c-form__helper-text">
${msg("URL which will be opened when a user clicks on the application.")}
</p>
</ak-form-element-horizontal>
</form>
`;
}
}

View file

@ -0,0 +1,26 @@
import BasePanel from "../BasePanel";
export class ApplicationWizardProviderPageBase extends BasePanel {
handleChange(ev: InputEvent) {
if (!ev.target) {
console.warn(`Received event with no target: ${ev}`);
return;
}
const target = ev.target as HTMLInputElement;
const value = target.type === "checkbox" ? target.checked : target.value;
this.dispatchWizardUpdate({
update: {
provider: {
[target.name]: value,
},
},
status: this.form.checkValidity() ? "valid" : "invalid",
});
}
validator() {
return this.form.reportValidity();
}
}
export default ApplicationWizardProviderPageBase;

View file

@ -0,0 +1,29 @@
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import BasePanel from "../BasePanel";
import { providerRendererList } from "../auth-method-choice/ak-application-wizard-authentication-method-choice.choices";
import "./ldap/ak-application-wizard-authentication-by-ldap";
import "./oauth/ak-application-wizard-authentication-by-oauth";
import "./proxy/ak-application-wizard-authentication-for-forward-domain-proxy";
import "./proxy/ak-application-wizard-authentication-for-reverse-proxy";
import "./proxy/ak-application-wizard-authentication-for-single-forward-proxy";
import "./radius/ak-application-wizard-authentication-by-radius";
import "./saml/ak-application-wizard-authentication-by-saml-configuration";
import "./scim/ak-application-wizard-authentication-by-scim";
// prettier-ignore
@customElement("ak-application-wizard-authentication-method")
export class ApplicationWizardApplicationDetails extends BasePanel {
render() {
const handler = providerRendererList.get(this.wizard.providerModel);
if (!handler) {
throw new Error(
"Unrecognized authentication method in ak-application-wizard-authentication-method",
);
}
return handler();
}
}
export default ApplicationWizardApplicationDetails;

View file

@ -0,0 +1,64 @@
import { msg } from "@lit/localize";
import { html } from "lit";
import { LDAPAPIAccessMode } from "@goauthentik/api";
export const bindModeOptions = [
{
label: msg("Cached binding"),
value: LDAPAPIAccessMode.Cached,
default: true,
description: html`${msg(
"Flow is executed and session is cached in memory. Flow is executed when session expires",
)}`,
},
{
label: msg("Direct binding"),
value: LDAPAPIAccessMode.Direct,
description: html`${msg(
"Always execute the configured bind flow to authenticate the user",
)}`,
},
];
export const searchModeOptions = [
{
label: msg("Cached querying"),
value: LDAPAPIAccessMode.Cached,
default: true,
description: html`${msg(
"The outpost holds all users and groups in-memory and will refresh every 5 Minutes",
)}`,
},
{
label: msg("Direct querying"),
value: LDAPAPIAccessMode.Direct,
description: html`${msg(
"Always returns the latest data, but slower than cached querying",
)}`,
},
];
export const mfaSupportHelp = msg(
"When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.",
);
export const groupHelp = msg(
"The start for gidNumbers, this number is added to a number generated from the group.Pk to make sure that the numbers aren't too low for POSIX groups. Default is 4000 to ensure that we don't collide with local groups or users primary groups gidNumber",
);
export const cryptoCertificateHelp = msg(
"The certificate for the above configured Base DN. As a fallback, the provider uses a self-signed certificate.",
);
export const tlsServerNameHelp = msg(
"DNS name for which the above configured certificate should be used. The certificate cannot be detected based on the base DN, as the SSL/TLS negotiation happens before such data is exchanged.",
);
export const uidStartNumberHelp = msg(
"The start for uidNumbers, this number is added to the user.Pk to make sure that the numbers aren't too low for POSIX users. Default is 2000 to ensure that we don't collide with local users uidNumber",
);
export const gidStartNumberHelp = msg(
"The start for gidNumbers, this number is added to a number generated from the group.Pk to make sure that the numbers aren't too low for POSIX groups. Default is 4000 to ensure that we don't collide with local groups or users primary groups gidNumber",
);

View file

@ -0,0 +1,146 @@
import "@goauthentik/admin/common/ak-core-group-search";
import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-number-input";
import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";
import { rootInterface } from "@goauthentik/elements/Base";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { html, nothing } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import { FlowsInstancesListDesignationEnum } from "@goauthentik/api";
import type { LDAPProvider } from "@goauthentik/api";
import BaseProviderPanel from "../BaseProviderPanel";
import {
bindModeOptions,
cryptoCertificateHelp,
gidStartNumberHelp,
groupHelp,
mfaSupportHelp,
searchModeOptions,
tlsServerNameHelp,
uidStartNumberHelp,
} from "./LDAPOptionsAndHelp";
@customElement("ak-application-wizard-authentication-by-ldap")
export class ApplicationWizardApplicationDetails extends BaseProviderPanel {
render() {
const provider = this.wizard.provider as LDAPProvider | undefined;
return html` <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
<ak-text-input
name="name"
value=${ifDefined(provider?.name)}
label=${msg("Name")}
required
help=${msg("Method's display Name.")}
></ak-text-input>
<ak-form-element-horizontal
label=${msg("Bind flow")}
?required=${true}
name="authorizationFlow"
>
<ak-tenanted-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${provider?.authorizationFlow}
.tenantFlow=${rootInterface()?.tenant?.flowAuthentication}
required
></ak-tenanted-flow-search>
<p class="pf-c-form__helper-text">${msg("Flow used for users to authenticate.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Search group")} name="searchGroup">
<ak-core-group-search
name="searchGroup"
group=${ifDefined(provider?.searchGroup ?? nothing)}
></ak-core-group-search>
<p class="pf-c-form__helper-text">${groupHelp}</p>
</ak-form-element-horizontal>
<ak-radio-input
label=${msg("Bind mode")}
name="bindMode"
.options=${bindModeOptions}
.value=${provider?.bindMode}
help=${msg("Configure how the outpost authenticates requests.")}
>
</ak-radio-input>
<ak-radio-input
label=${msg("Search mode")}
name="searchMode"
.options=${searchModeOptions}
.value=${provider?.searchMode}
help=${msg("Configure how the outpost queries the core authentik server's users.")}
>
</ak-radio-input>
<ak-switch-input
name="openInNewTab"
label=${msg("Code-based MFA Support")}
?checked=${provider?.mfaSupport}
help=${mfaSupportHelp}
>
</ak-switch-input>
<ak-form-group .expanded=${true}>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-text-input
name="baseDn"
label=${msg("Base DN")}
required
value="${first(provider?.baseDn, "DC=ldap,DC=goauthentik,DC=io")}"
help=${msg(
"LDAP DN under which bind requests and search requests can be made.",
)}
>
</ak-text-input>
<ak-form-element-horizontal label=${msg("Certificate")} name="certificate">
<ak-crypto-certificate-search
certificate=${ifDefined(provider?.certificate ?? nothing)}
name="certificate"
>
</ak-crypto-certificate-search>
<p class="pf-c-form__helper-text">${cryptoCertificateHelp}</p>
</ak-form-element-horizontal>
<ak-text-input
label=${msg("TLS Server name")}
name="tlsServerName"
value="${first(provider?.tlsServerName, "")}"
help=${tlsServerNameHelp}
></ak-text-input>
<ak-number-input
label=${msg("UID start number")}
required
name="uidStartNumber"
value="${first(provider?.uidStartNumber, 2000)}"
help=${uidStartNumberHelp}
></ak-number-input>
<ak-number-input
label=${msg("GID start number")}
required
name="gidStartNumber"
value="${first(provider?.gidStartNumber, 4000)}"
help=${gidStartNumberHelp}
></ak-number-input>
</div>
</ak-form-group>
</form>`;
}
}
export default ApplicationWizardApplicationDetails;

View file

@ -0,0 +1,301 @@
import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search";
import {
clientTypeOptions,
issuerModeOptions,
redirectUriHelp,
subjectModeOptions,
} from "@goauthentik/admin/providers/oauth2/OAuth2ProviderForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-number-input";
import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";
import "@goauthentik/components/ak-textarea-input";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { customElement, state } from "@lit/reactive-element/decorators.js";
import { html, nothing } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import {
ClientTypeEnum,
FlowsInstancesListDesignationEnum,
PropertymappingsApi,
SourcesApi,
} from "@goauthentik/api";
import type {
OAuth2Provider,
PaginatedOAuthSourceList,
PaginatedScopeMappingList,
} from "@goauthentik/api";
import BaseProviderPanel from "../BaseProviderPanel";
@customElement("ak-application-wizard-authentication-by-oauth")
export class ApplicationWizardAuthenticationByOauth extends BaseProviderPanel {
@state()
showClientSecret = false;
@state()
propertyMappings?: PaginatedScopeMappingList;
@state()
oauthSources?: PaginatedOAuthSourceList;
constructor() {
super();
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsScopeList({
ordering: "scope_name",
})
.then((propertyMappings: PaginatedScopeMappingList) => {
this.propertyMappings = propertyMappings;
});
new SourcesApi(DEFAULT_CONFIG)
.sourcesOauthList({
ordering: "name",
hasJwks: true,
})
.then((oauthSources: PaginatedOAuthSourceList) => {
this.oauthSources = oauthSources;
});
}
render() {
const provider = this.wizard.provider as OAuth2Provider | undefined;
return html`<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
<ak-text-input
name="name"
label=${msg("Name")}
value=${ifDefined(provider?.name)}
required
></ak-text-input>
<ak-form-element-horizontal
name="authenticationFlow"
label=${msg("Authentication flow")}
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${provider?.authenticationFlow}
required
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used when a user access this provider and is not authenticated.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
name="authorizationFlow"
label=${msg("Authorization flow")}
?required=${true}
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authorization}
.currentFlow=${provider?.authorizationFlow}
required
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used when authorizing this provider.")}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-radio-input
name="clientType"
label=${msg("Client type")}
.value=${provider?.clientType}
required
@change=${(ev: CustomEvent<ClientTypeEnum>) => {
this.showClientSecret = ev.detail !== ClientTypeEnum.Public;
}}
.options=${clientTypeOptions}
>
</ak-radio-input>
<ak-text-input
name="clientId"
label=${msg("Client ID")}
value="${first(
provider?.clientId,
randomString(40, ascii_letters + digits),
)}"
required
>
</ak-text-input>
<ak-text-input
name="clientSecret"
label=${msg("Client Secret")}
value="${first(
provider?.clientSecret,
randomString(128, ascii_letters + digits),
)}"
?hidden=${!this.showClientSecret}
>
</ak-text-input>
<ak-textarea-input
name="redirectUris"
label=${msg("Redirect URIs/Origins (RegEx)")}
.value=${provider?.redirectUris}
.bighelp=${redirectUriHelp}
>
</ak-textarea-input>
<ak-form-element-horizontal label=${msg("Signing Key")} name="signingKey">
<ak-crypto-certificate-search
certificate=${ifDefined(provider?.signingKey ?? nothing)}
name="certificate"
singleton
>
</ak-crypto-certificate-search>
<p class="pf-c-form__helper-text">${msg("Key used to sign the tokens.")}</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header"> ${msg("Advanced protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-text-input
name="accessCodeValidity"
label=${msg("Access code validity")}
required
value="${first(provider?.accessCodeValidity, "minutes=1")}"
.bighelp=${html`<p class="pf-c-form__helper-text">
${msg("Configure how long access codes are valid for.")}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>`}
>
</ak-text-input>
<ak-text-input
name="accessTokenValidity"
label=${msg("Access Token validity")}
value="${first(provider?.accessTokenValidity, "minutes=5")}"
required
.bighelp=${html` <p class="pf-c-form__helper-text">
${msg("Configure how long access tokens are valid for.")}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>`}
>
</ak-text-input>
<ak-text-input
name="refreshTokenValidity"
label=${msg("Refresh Token validity")}
value="${first(provider?.refreshTokenValidity, "days=30")}"
?required=${true}
.bighelp=${html` <p class="pf-c-form__helper-text">
${msg("Configure how long refresh tokens are valid for.")}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>`}
>
</ak-text-input>
<ak-form-element-horizontal label=${msg("Scopes")} name="propertyMappings">
<select class="pf-c-form-control" multiple>
${this.propertyMappings?.results.map((scope) => {
let selected = false;
if (!provider?.propertyMappings) {
selected =
scope.managed?.startsWith(
"goauthentik.io/providers/oauth2/scope-",
) || false;
} else {
selected = Array.from(provider?.propertyMappings).some((su) => {
return su == scope.pk;
});
}
return html`<option
value=${ifDefined(scope.pk)}
?selected=${selected}
>
${scope.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${msg(
"Select which scopes can be used by the client. The client still has to specify the scope to access the data.",
)}
</p>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal>
<ak-radio-input
name="subMode"
label=${msg("Subject mode")}
required
.options=${subjectModeOptions}
.value=${provider?.subMode}
help=${msg(
"Configure what data should be used as unique User Identifier. For most cases, the default should be fine.",
)}
>
</ak-radio-input>
<ak-switch-input name="includeClaimsInIdToken">
label=${msg("Include claims in id_token")}
?checked=${first(provider?.includeClaimsInIdToken, true)}
help=${msg(
"Include User claims from scopes in the id_token, for applications that don't access the userinfo endpoint.",
)}></ak-switch-input
>
<ak-radio-input
name="issuerMode"
label=${msg("Issuer mode")}
required
.options=${issuerModeOptions}
.value=${provider?.issuerMode}
help=${msg(
"Configure how the issuer field of the ID Token should be filled.",
)}
>
</ak-radio-input>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header">${msg("Machine-to-Machine authentication settings")}</span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Trusted OIDC Sources")}
name="jwksSources"
>
<select class="pf-c-form-control" multiple>
${this.oauthSources?.results.map((source) => {
const selected = (provider?.jwksSources || []).some((su) => {
return su == source.pk;
});
return html`<option value=${source.pk} ?selected=${selected}>
${source.name} (${source.slug})
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${msg(
"JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider.",
)}
</p>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}
export default ApplicationWizardAuthenticationByOauth;

View file

@ -0,0 +1,255 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";
import "@goauthentik/components/ak-textarea-input";
import "@goauthentik/components/ak-toggle-group";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { state } from "@lit/reactive-element/decorators.js";
import { TemplateResult, html, nothing } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import {
FlowsInstancesListDesignationEnum,
PaginatedOAuthSourceList,
PaginatedScopeMappingList,
PropertymappingsApi,
ProxyMode,
ProxyProvider,
SourcesApi,
} from "@goauthentik/api";
import BaseProviderPanel from "../BaseProviderPanel";
type MaybeTemplateResult = TemplateResult | typeof nothing;
export class AkTypeProxyApplicationWizardPage extends BaseProviderPanel {
constructor() {
super();
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsScopeList({ ordering: "scope_name" })
.then((propertyMappings: PaginatedScopeMappingList) => {
this.propertyMappings = propertyMappings;
});
new SourcesApi(DEFAULT_CONFIG)
.sourcesOauthList({
ordering: "name",
hasJwks: true,
})
.then((oauthSources: PaginatedOAuthSourceList) => {
this.oauthSources = oauthSources;
});
}
propertyMappings?: PaginatedScopeMappingList;
oauthSources?: PaginatedOAuthSourceList;
@state()
showHttpBasic = true;
@state()
mode: ProxyMode = ProxyMode.Proxy;
get instance(): ProxyProvider | undefined {
return this.wizard.provider as ProxyProvider;
}
renderModeDescription(): MaybeTemplateResult {
return nothing;
}
renderProxyMode() {
return html`<h2>This space intentionally left blank</h2>`;
}
renderHttpBasic(): TemplateResult {
return html`<ak-text-input
name="basicAuthUserAttribute"
label=${msg("HTTP-Basic Username Key")}
value="${ifDefined(this.instance?.basicAuthUserAttribute)}"
help=${msg(
"User/Group Attribute used for the user part of the HTTP-Basic Header. If not set, the user's Email address is used.",
)}
>
</ak-text-input>
<ak-text-input
name="basicAuthPasswordAttribute"
label=${msg("HTTP-Basic Password Key")}
value="${ifDefined(this.instance?.basicAuthPasswordAttribute)}"
help=${msg(
"User/Group Attribute used for the password part of the HTTP-Basic Header.",
)}
>
</ak-text-input>`;
}
render() {
return html`<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
${this.renderModeDescription()}
<ak-text-input
name="name"
value=${ifDefined(this.instance?.name)}
required
label=${msg("Name")}
></ak-text-input>
<ak-form-element-horizontal
label=${msg("Authentication flow")}
?required=${false}
name="authenticationFlow"
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${this.instance?.authenticationFlow}
required
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used when a user access this provider and is not authenticated.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Authorization flow")}
?required=${true}
name="authorizationFlow"
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authorization}
.currentFlow=${this.instance?.authorizationFlow}
required
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used when authorizing this provider.")}
</p>
</ak-form-element-horizontal>
${this.renderProxyMode()}
<ak-text-input
name="accessTokenValidity"
value=${first(this.instance?.accessTokenValidity, "hours=24")}
label=${msg("Token validity")}
help=${msg("Configure how long tokens are valid for.")}
></ak-text-input>
<ak-form-group>
<span slot="header">${msg("Advanced protocol settings")}</span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("Certificate")} name="certificate">
<ak-crypto-certificate-search
certificate=${ifDefined(this.instance?.certificate ?? undefined)}
></ak-crypto-certificate-search>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Additional scopes")}
name="propertyMappings"
>
<select class="pf-c-form-control" multiple>
${this.propertyMappings?.results
.filter((scope) => {
return !scope.managed?.startsWith("goauthentik.io/providers");
})
.map((scope) => {
const selected = (this.instance?.propertyMappings || []).some(
(su) => {
return su == scope.pk;
},
);
return html`<option
value=${ifDefined(scope.pk)}
?selected=${selected}
>
${scope.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${msg("Additional scope mappings, which are passed to the proxy.")}
</p>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal>
<ak-textarea-input
name="skipPathRegex"
label=${this.mode === ProxyMode.ForwardDomain
? msg("Unauthenticated URLs")
: msg("Unauthenticated Paths")}
value=${ifDefined(this.instance?.skipPathRegex)}
.bighelp=${html` <p class="pf-c-form__helper-text">
${msg(
"Regular expressions for which authentication is not required. Each new line is interpreted as a new expression.",
)}
</p>
<p class="pf-c-form__helper-text">
${msg(
"When using proxy or forward auth (single application) mode, the requested URL Path is checked against the regular expressions. When using forward auth (domain mode), the full requested URL including scheme and host is matched against the regular expressions.",
)}
</p>`}
>
</ak-textarea-input>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header">${msg("Authentication settings")}</span>
<div slot="body" class="pf-c-form">
<ak-switch-input
name="interceptHeaderAuth"
?checked=${first(this.instance?.interceptHeaderAuth, true)}
label=${msg("Intercept header authentication")}
help=${msg(
"When enabled, authentik will intercept the Authorization header to authenticate the request.",
)}
></ak-switch-input>
<ak-switch-input
name="basicAuthEnabled"
?checked=${first(this.instance?.basicAuthEnabled, false)}
@change=${(ev: Event) => {
const el = ev.target as HTMLInputElement;
this.showHttpBasic = el.checked;
}}
label=${msg("Send HTTP-Basic Authentication")}
help=${msg(
"Send a custom HTTP-Basic Authentication header based on values from authentik.",
)}
></ak-switch-input>
${this.showHttpBasic ? this.renderHttpBasic() : html``}
<ak-form-element-horizontal
label=${msg("Trusted OIDC Sources")}
name="jwksSources"
>
<select class="pf-c-form-control" multiple>
${this.oauthSources?.results.map((source) => {
const selected = (this.instance?.jwksSources || []).some((su) => {
return su == source.pk;
});
return html`<option value=${source.pk} ?selected=${selected}>
${source.name} (${source.slug})
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${msg(
"JWTs signed by certificates configured in the selected sources can be used to authenticate to this provider.",
)}
</p>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}
export default AkTypeProxyApplicationWizardPage;

View file

@ -0,0 +1,55 @@
import "@goauthentik/components/ak-text-input";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators.js";
import { html } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import AkTypeProxyApplicationWizardPage from "./AuthenticationByProxyPage";
@customElement("ak-application-wizard-authentication-for-forward-proxy-domain")
export class AkForwardDomainProxyApplicationWizardPage extends AkTypeProxyApplicationWizardPage {
renderModeDescription() {
return html`<p class="pf-u-mb-xl">
${msg(
"Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application.",
)}
</p>
<div class="pf-u-mb-xl">
${msg("An example setup can look like this:")}
<ul class="pf-c-list">
<li>${msg("authentik running on auth.example.com")}</li>
<li>${msg("app1 running on app1.example.com")}</li>
</ul>
${msg(
"In this case, you'd set the Authentication URL to auth.example.com and Cookie domain to example.com.",
)}
</div>`;
}
renderProxyMode() {
return html`
<ak-text-input
name="externalHost"
label=${msg("External host")}
value=${ifDefined(this.instance?.externalHost)}
required
help=${msg(
"The external URL you'll authenticate at. The authentik core server should be reachable under this URL.",
)}
>
</ak-text-input>
<ak-text-input
name="cookieDomain"
label=${msg("Cookie domain")}
value="${ifDefined(this.instance?.cookieDomain)}"
required
help=${msg(
"Set this to the domain you wish the authentication to be valid for. Must be a parent domain of the URL above. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'.",
)}
></ak-text-input>
`;
}
}
export default AkForwardDomainProxyApplicationWizardPage;

View file

@ -0,0 +1,49 @@
import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators.js";
import { html } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import AkTypeProxyApplicationWizardPage from "./AuthenticationByProxyPage";
@customElement("ak-application-wizard-authentication-for-reverse-proxy")
export class AkReverseProxyApplicationWizardPage extends AkTypeProxyApplicationWizardPage {
renderModeDescription() {
return html`<p class="pf-u-mb-xl">
${msg(
"This provider will behave like a transparent reverse-proxy, except requests must be authenticated. If your upstream application uses HTTPS, make sure to connect to the outpost using HTTPS as well.",
)}
</p>`;
}
renderProxyMode() {
return html` <ak-text-input
name="externalHost"
value=${ifDefined(this.instance?.externalHost)}
required
label=${msg("External host")}
help=${msg(
"The external URL you'll access the application at. Include any non-standard port.",
)}
></ak-text-input>
<ak-text-input
name="internalHost"
value=${ifDefined(this.instance?.internalHost)}
required
label=${msg("Internal host")}
help=${msg("Upstream host that the requests are forwarded to.")}
></ak-text-input>
<ak-switch-input
name="internalHostSslValidation"
?checked=${first(this.instance?.internalHostSslValidation, true)}
label=${msg("Internal host SSL Validation")}
help=${msg("Validate SSL Certificates of upstream servers.")}
>
</ak-switch-input>`;
}
}
export default AkReverseProxyApplicationWizardPage;

View file

@ -0,0 +1,36 @@
import "@goauthentik/components/ak-text-input";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators.js";
import { html } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import AkTypeProxyApplicationWizardPage from "./AuthenticationByProxyPage";
@customElement("ak-application-wizard-authentication-for-single-forward-proxy")
export class AkForwardSingleProxyApplicationWizardPage extends AkTypeProxyApplicationWizardPage {
renderModeDescription() {
return html`<p class="pf-u-mb-xl">
${msg(
html`Use this provider with nginx's <code>auth_request</code> or traefik's
<code>forwardAuth</code>. Each application/domain needs its own provider.
Additionally, on each domain, <code>/outpost.goauthentik.io</code> must be
routed to the outpost (when using a managed outpost, this is done for you).`,
)}
</p>`;
}
renderProxyMode() {
return html`<ak-text-input
name="externalHost"
value=${ifDefined(this.instance?.externalHost)}
required
label=${msg("External host")}
help=${msg(
"The external URL you'll access the application at. Include any non-standard port.",
)}
></ak-text-input>`;
}
}
export default AkForwardSingleProxyApplicationWizardPage;

View file

@ -0,0 +1,73 @@
import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search";
import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-text-input";
import { rootInterface } from "@goauthentik/elements/Base";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators.js";
import { html } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import { FlowsInstancesListDesignationEnum, RadiusProvider } from "@goauthentik/api";
import BaseProviderPanel from "../BaseProviderPanel";
@customElement("ak-application-wizard-authentication-by-radius")
export class ApplicationWizardAuthenticationByRadius extends BaseProviderPanel {
render() {
const provider = this.wizard.provider as RadiusProvider | undefined;
return html`<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
<ak-text-input
name="name"
label=${msg("Name")}
value=${ifDefined(provider?.name)}
required
>
</ak-text-input>
<ak-form-element-horizontal
label=${msg("Authentication flow")}
?required=${true}
name="authorizationFlow"
>
<ak-tenanted-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${provider?.authorizationFlow}
.tenantFlow=${rootInterface()?.tenant?.flowAuthentication}
required
></ak-tenanted-flow-search>
<p class="pf-c-form__helper-text">${msg("Flow used for users to authenticate.")}</p>
</ak-form-element-horizontal>
<ak-form-group expanded>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-text-input
name="sharedSecret"
label=${msg("Shared secret")}
value=${first(
provider?.sharedSecret,
randomString(128, ascii_letters + digits),
)}
required
></ak-text-input>
<ak-text-input
name="clientNetworks"
label=${msg("Client Networks")}
value=${first(provider?.clientNetworks, "0.0.0.0/0, ::/0")}
required
help=${msg(`List of CIDRs (comma-seperated) that clients can connect from. A more specific
CIDR will match before a looser one. Clients connecting from a non-specified CIDR
will be dropped.`)}
></ak-text-input>
</div>
</ak-form-group>
</form>`;
}
}
export default ApplicationWizardAuthenticationByRadius;

View file

@ -0,0 +1,33 @@
import { msg } from "@lit/localize";
import { DigestAlgorithmEnum, SignatureAlgorithmEnum, SpBindingEnum } from "@goauthentik/api";
type Option<T> = [string, T, boolean?];
function toOptions<T>(options: Option<T>[]) {
return options.map(([label, value, isDefault]: Option<T>) => ({
label,
value,
default: isDefault ?? false,
}));
}
export const spBindingOptions = toOptions([
[msg("Redirect"), SpBindingEnum.Redirect, true],
[msg("Post"), SpBindingEnum.Post],
]);
export const digestAlgorithmOptions = toOptions([
["SHA1", DigestAlgorithmEnum._200009Xmldsigsha1],
["SHA256", DigestAlgorithmEnum._200104Xmlencsha256, true],
["SHA384", DigestAlgorithmEnum._200104XmldsigMoresha384],
["SHA512", DigestAlgorithmEnum._200104Xmlencsha512],
]);
export const signatureAlgorithmOptions = toOptions([
["RSA-SHA1", SignatureAlgorithmEnum._200009XmldsigrsaSha1],
["RSA-SHA256", SignatureAlgorithmEnum._200104XmldsigMorersaSha256, true],
["RSA-SHA384", SignatureAlgorithmEnum._200104XmldsigMorersaSha384],
["RSA-SHA512", SignatureAlgorithmEnum._200104XmldsigMorersaSha512],
["DSA-SHA1", SignatureAlgorithmEnum._200009XmldsigdsaSha1],
]);

View file

@ -0,0 +1,250 @@
import "@goauthentik/admin/common/ak-core-group-search";
import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/components/ak-number-input";
import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { html } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import {
FlowsInstancesListDesignationEnum,
PaginatedSAMLPropertyMappingList,
PropertymappingsApi,
SAMLProvider,
} from "@goauthentik/api";
import BaseProviderPanel from "../BaseProviderPanel";
import {
digestAlgorithmOptions,
signatureAlgorithmOptions,
spBindingOptions,
} from "./SamlProviderOptions";
@customElement("ak-application-wizard-authentication-by-saml-configuration")
export class ApplicationWizardProviderSamlConfiguration extends BaseProviderPanel {
propertyMappings?: PaginatedSAMLPropertyMappingList;
constructor() {
super();
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsSamlList({
ordering: "saml_name",
})
.then((propertyMappings: PaginatedSAMLPropertyMappingList) => {
this.propertyMappings = propertyMappings;
});
}
render() {
const provider = this.wizard.provider as SAMLProvider | undefined;
return html` <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
<ak-text-input
name="name"
value=${ifDefined(provider?.name)}
required
label=${msg("Name")}
></ak-text-input>
<ak-form-element-horizontal
label=${msg("Authentication flow")}
?required=${false}
name="authenticationFlow"
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authentication}
.currentFlow=${provider?.authenticationFlow}
required
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used when a user access this provider and is not authenticated.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Authorization flow")}
?required=${true}
name="authorizationFlow"
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authorization}
.currentFlow=${provider?.authorizationFlow}
required
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used when authorizing this provider.")}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-text-input
name="acsUrl"
value=${ifDefined(provider?.acsUrl)}
required
label=${msg("ACS URL")}
></ak-text-input>
<ak-text-input
name="issuer"
value=${provider?.issuer || "authentik"}
required
label=${msg("Issuer")}
help=${msg("Also known as EntityID.")}
></ak-text-input>
<ak-radio-input
name="spBinding"
label=${msg("Service Provider Binding")}
required
.options=${spBindingOptions}
.value=${provider?.spBinding}
help=${msg(
"Determines how authentik sends the response back to the Service Provider.",
)}
>
</ak-radio-input>
<ak-text-input
name="audience"
value=${ifDefined(provider?.audience)}
label=${msg("Audience")}
></ak-text-input>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header"> ${msg("Advanced protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Signing Certificate")}
name="signingKp"
>
<ak-crypto-certificate-search
certificate=${ifDefined(provider?.signingKp ?? undefined)}
></ak-crypto-certificate-search>
<p class="pf-c-form__helper-text">
${msg(
"Certificate used to sign outgoing Responses going to the Service Provider.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Verification Certificate")}
name="verificationKp"
>
<ak-crypto-certificate-search
certificate=${ifDefined(provider?.verificationKp ?? undefined)}
nokey
></ak-crypto-certificate-search>
<p class="pf-c-form__helper-text">
${msg(
"When selected, incoming assertion's Signatures will be validated against this certificate. To allow unsigned Requests, leave on default.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Property mappings")}
?required=${true}
name="propertyMappings"
>
<select class="pf-c-form-control" multiple>
${this.propertyMappings?.results.map((mapping) => {
let selected = false;
if (!provider?.propertyMappings) {
selected =
mapping.managed?.startsWith(
"goauthentik.io/providers/saml",
) || false;
} else {
selected = Array.from(provider?.propertyMappings).some((su) => {
return su == mapping.pk;
});
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("NameID Property Mapping")}
name="nameIdMapping"
>
<ak-saml-property-mapping-search
name="nameIdMapping"
propertymapping=${ifDefined(provider?.nameIdMapping ?? undefined)}
></ak-saml-property-mapping-search>
<p class="pf-c-form__helper-text">
${msg(
"Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected.",
)}
</p>
</ak-form-element-horizontal>
<ak-text-input
name="assertionValidNotBefore"
value=${provider?.assertionValidNotBefore || "minutes=-5"}
required
label=${msg("Assertion valid not before")}
help=${msg("Configure the maximum allowed time drift for an assertion.")}
></ak-text-input>
<ak-text-input
name="assertionValidNotOnOrAfter"
value=${provider?.assertionValidNotOnOrAfter || "minutes=5"}
required
label=${msg("Assertion valid not on or after")}
help=${msg("Assertion not valid on or after current time + this value.")}
></ak-text-input>
<ak-text-input
name="sessionValidNotOnOrAfter"
value=${provider?.sessionValidNotOnOrAfter || "minutes=86400"}
required
label=${msg("Session valid not on or after")}
help=${msg("Session not valid on or after current time + this value.")}
></ak-text-input>
<ak-radio-input
name="digestAlgorithm"
label=${msg("Digest algorithm")}
required
.options=${digestAlgorithmOptions}
.value=${provider?.digestAlgorithm}
>
</ak-radio-input>
<ak-radio-input
name="signatureAlgorithm"
label=${msg("Signature algorithm")}
required
.options=${signatureAlgorithmOptions}
.value=${provider?.signatureAlgorithm}
>
</ak-radio-input>
</div>
</ak-form-group>
</form>`;
}
}
export default ApplicationWizardProviderSamlConfiguration;

View file

@ -0,0 +1,81 @@
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search-no-default";
import "@goauthentik/components/ak-file-input";
import { AkFileInput } from "@goauthentik/components/ak-file-input";
import "@goauthentik/components/ak-text-input";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { html } from "lit";
import { query } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import {
FlowsInstancesListDesignationEnum,
ProvidersSamlImportMetadataCreateRequest,
} from "@goauthentik/api";
import BaseProviderPanel from "../BaseProviderPanel";
@customElement("ak-application-wizard-authentication-by-saml-import")
export class ApplicationWizardProviderSamlImport extends BaseProviderPanel {
@query('ak-file-input[name="metadata"]')
fileInput!: AkFileInput;
handleChange(ev: InputEvent) {
if (!ev.target) {
console.warn(`Received event with no target: ${ev}`);
return;
}
const target = ev.target as HTMLInputElement;
if (target.type === "file") {
const file = this.fileInput.files?.[0] ?? null;
if (file) {
this.dispatchWizardUpdate({
update: {
provider: {
file,
},
},
status: this.form.checkValidity() ? "valid" : "invalid",
});
}
return;
}
super.handleChange(ev);
}
render() {
const provider = this.wizard.provider as
| ProvidersSamlImportMetadataCreateRequest
| undefined;
return html` <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
<ak-text-input
name="name"
value=${ifDefined(provider?.name)}
label=${msg("Name")}
required
help=${msg("Method's display Name.")}
></ak-text-input>
<ak-form-element-horizontal
label=${msg("Authorization flow")}
?required=${true}
name="authorizationFlow"
>
<ak-flow-search-no-default
flowType=${FlowsInstancesListDesignationEnum.Authorization}
required
></ak-flow-search-no-default>
<p class="pf-c-form__helper-text">
${msg("Flow used when authorizing this provider.")}
</p>
</ak-form-element-horizontal>
<ak-file-input name="metadata" label=${msg("Metadata")} required></ak-file-input>
</form>`;
}
}
export default ApplicationWizardProviderSamlImport;

View file

@ -0,0 +1,112 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { AKElement } from "@goauthentik/elements/Base";
import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect";
import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter";
import { html } from "lit";
import { customElement } from "lit/decorators.js";
import { property, query } from "lit/decorators.js";
import {
PropertymappingsApi,
PropertymappingsSamlListRequest,
SAMLPropertyMapping,
} from "@goauthentik/api";
async function fetchObjects(query?: string): Promise<SAMLPropertyMapping[]> {
const args: PropertymappingsSamlListRequest = {
ordering: "saml_name",
};
if (query !== undefined) {
args.search = query;
}
const items = await new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsSamlList(args);
return items.results;
}
function renderElement(item: SAMLPropertyMapping): string {
return item.name;
}
function renderValue(item: SAMLPropertyMapping | undefined): string | undefined {
return item?.pk;
}
/**
* SAML Property Mapping Search
*
* @element ak-saml-property-mapping-search
*
* A wrapper around SearchSelect for the SAML Property Search. It's a unique search, but for the
* purpose of the form all you need to know is that it is being searched and selected. Let's put the
* how somewhere else.
*
*/
@customElement("ak-saml-property-mapping-search")
export class SAMLPropertyMappingSearch extends CustomListenerElement(AKElement) {
/**
* The current property mapping known to the caller.
*
* @attr
*/
@property({ type: String, reflect: true, attribute: "propertymapping" })
propertyMapping?: string;
@query("ak-search-select")
search!: SearchSelect<SAMLPropertyMapping>;
@property({ type: String })
name: string | null | undefined;
selectedPropertyMapping?: SAMLPropertyMapping;
constructor() {
super();
this.selected = this.selected.bind(this);
this.handleSearchUpdate = this.handleSearchUpdate.bind(this);
this.addCustomListener("ak-change", this.handleSearchUpdate);
}
get value() {
return this.selectedPropertyMapping ? renderValue(this.selectedPropertyMapping) : undefined;
}
connectedCallback() {
super.connectedCallback();
const horizontalContainer = this.closest("ak-form-element-horizontal[name]");
if (!horizontalContainer) {
throw new Error("This search can only be used in a named ak-form-element-horizontal");
}
const name = horizontalContainer.getAttribute("name");
const myName = this.getAttribute("name");
if (name !== null && name !== myName) {
this.setAttribute("name", name);
}
}
handleSearchUpdate(ev: CustomEvent) {
ev.stopPropagation();
this.selectedPropertyMapping = ev.detail.value;
this.dispatchEvent(new InputEvent("input", { bubbles: true, composed: true }));
}
selected(item: SAMLPropertyMapping): boolean {
return this.propertyMapping === item.pk;
}
render() {
return html`
<ak-search-select
.fetchObjects=${fetchObjects}
.renderElement=${renderElement}
.value=${renderValue}
.selected=${this.selected}
blankable
>
</ak-search-select>
`;
}
}
export default SAMLPropertyMappingSearch;

View file

@ -0,0 +1,189 @@
import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { customElement, state } from "@lit/reactive-element/decorators.js";
import { html } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import {
CoreApi,
CoreGroupsListRequest,
type Group,
PaginatedSCIMMappingList,
PropertymappingsApi,
type SCIMProvider,
} from "@goauthentik/api";
import BaseProviderPanel from "../BaseProviderPanel";
@customElement("ak-application-wizard-authentication-by-scim")
export class ApplicationWizardAuthenticationBySCIM extends BaseProviderPanel {
@state()
propertyMappings?: PaginatedSCIMMappingList;
constructor() {
super();
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsScopeList({
ordering: "scope_name",
})
.then((propertyMappings: PaginatedSCIMMappingList) => {
this.propertyMappings = propertyMappings;
});
}
render() {
const provider = this.wizard.provider as SCIMProvider | undefined;
return html`<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
<ak-text-input
name="name"
label=${msg("Name")}
value=${ifDefined(provider?.name)}
required
></ak-text-input>
<ak-form-group expanded>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-text-input
name="url"
label=${msg("URL")}
value="${first(provider?.url, "")}"
required
help=${msg("SCIM base url, usually ends in /v2.")}
>
</ak-text-input>
<ak-text-input
name="token"
label=${msg("Token")}
value="${first(provider?.token, "")}"
required
help=${msg(
"Token to authenticate with. Currently only bearer authentication is supported.",
)}
>
</ak-text-input>
</div>
</ak-form-group>
<ak-form-group expanded>
<span slot="header">${msg("User filtering")}</span>
<div slot="body" class="pf-c-form">
<ak-switch-input
name="excludeUsersServiceAccount"
?checked=${first(provider?.excludeUsersServiceAccount, true)}
label=${msg("Exclude service accounts")}
></ak-switch-input>
<ak-form-element-horizontal label=${msg("Group")} name="filterGroup">
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Group[]> => {
const args: CoreGroupsListRequest = {
ordering: "name",
};
if (query !== undefined) {
args.search = query;
}
const groups = await new CoreApi(DEFAULT_CONFIG).coreGroupsList(
args,
);
return groups.results;
}}
.renderElement=${(group: Group): string => {
return group.name;
}}
.value=${(group: Group | undefined): string | undefined => {
return group ? group.pk : undefined;
}}
.selected=${(group: Group): boolean => {
return group.pk === provider?.filterGroup;
}}
?blankable=${true}
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${msg("Only sync users within the selected group.")}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group ?expanded=${true}>
<span slot="header"> ${msg("Attribute mapping")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("User Property Mappings")}
?required=${true}
name="propertyMappings"
>
<select class="pf-c-form-control" multiple>
${this.propertyMappings?.results.map((mapping) => {
let selected = false;
if (!provider?.propertyMappings) {
selected =
mapping.managed === "goauthentik.io/providers/scim/user" ||
false;
} else {
selected = Array.from(provider?.propertyMappings).some((su) => {
return su == mapping.pk;
});
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${msg("Property mappings used to user mapping.")}
</p>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Group Property Mappings")}
?required=${true}
name="propertyMappingsGroup"
>
<select class="pf-c-form-control" multiple>
${this.propertyMappings?.results.map((mapping) => {
let selected = false;
if (!provider?.propertyMappingsGroup) {
selected =
mapping.managed === "goauthentik.io/providers/scim/group";
} else {
selected = Array.from(provider?.propertyMappingsGroup).some(
(su) => {
return su == mapping.pk;
},
);
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${msg("Property mappings used to group creation.")}
</p>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}
export default ApplicationWizardAuthenticationBySCIM;

View file

@ -1,35 +0,0 @@
import "@goauthentik/elements/forms/HorizontalFormElement";
import { WizardPage } from "@goauthentik/elements/wizard/WizardPage";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { CSSResult, TemplateResult, html } from "lit";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
@customElement("ak-application-wizard-type-oauth-api")
export class TypeOAuthAPIApplicationWizardPage extends WizardPage {
static get styles(): CSSResult[] {
return [PFBase, PFButton, PFForm, PFRadio];
}
sidebarLabel = () => msg("Method details");
render(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<p>
${msg(
"This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically.",
)}
</p>
<p>
${msg(
"By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password.",
)}
</p>
</form> `;
}
}

View file

@ -1,84 +0,0 @@
import "@goauthentik/elements/forms/HorizontalFormElement";
import { WizardPage } from "@goauthentik/elements/wizard/WizardPage";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { CSSResult, TemplateResult, html } from "lit";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { TypeCreate } from "@goauthentik/api";
@customElement("ak-application-wizard-type-oauth")
export class TypeOAuthApplicationWizardPage extends WizardPage {
applicationTypes: TypeCreate[] = [
{
component: "ak-application-wizard-type-oauth-code",
name: msg("Web application"),
description: msg(
"Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP)",
),
modelName: "",
},
{
component: "ak-application-wizard-type-oauth-implicit",
name: msg("Single-page applications"),
description: msg(
"Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue)",
),
modelName: "",
},
{
component: "ak-application-wizard-type-oauth-implicit",
name: msg("Native application"),
description: msg(
"Applications which redirect users to a non-web callback (for example, Android, iOS)",
),
modelName: "",
},
{
component: "ak-application-wizard-type-oauth-api",
name: msg("API"),
description: msg(
"Authentication without user interaction, or machine-to-machine authentication.",
),
modelName: "",
},
];
static get styles(): CSSResult[] {
return [PFBase, PFButton, PFForm, PFRadio];
}
sidebarLabel = () => msg("Application type");
render(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
${this.applicationTypes.map((type) => {
return html`<div class="pf-c-radio">
<input
class="pf-c-radio__input"
type="radio"
name="type"
id=${type.component}
@change=${() => {
this.host.steps = [
"ak-application-wizard-initial",
"ak-application-wizard-type",
"ak-application-wizard-type-oauth",
type.component,
];
this.host.state["oauth-type"] = type.component;
this.host.isValid = true;
}}
/>
<label class="pf-c-radio__label" for=${type.component}>${type.name}</label>
<span class="pf-c-radio__description">${type.description}</span>
</div>`;
})}
</form> `;
}
}

View file

@ -1,57 +0,0 @@
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search-no-default";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { KeyUnknown } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/SearchSelect";
import { WizardFormPage } from "@goauthentik/elements/wizard/WizardFormPage";
import "@goauthentik/elements/wizard/WizardFormPage";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { TemplateResult, html } from "lit";
import {
ClientTypeEnum,
FlowsInstancesListDesignationEnum,
OAuth2ProviderRequest,
ProvidersApi,
} from "@goauthentik/api";
@customElement("ak-application-wizard-type-oauth-code")
export class TypeOAuthCodeApplicationWizardPage extends WizardFormPage {
sidebarLabel = () => msg("Method details");
nextDataCallback = async (data: KeyUnknown): Promise<boolean> => {
this.host.addActionBefore(msg("Create provider"), async (): Promise<boolean> => {
const req: OAuth2ProviderRequest = {
name: this.host.state["name"] as string,
clientType: ClientTypeEnum.Confidential,
authorizationFlow: data.authorizationFlow as string,
};
const provider = await new ProvidersApi(DEFAULT_CONFIG).providersOauth2Create({
oAuth2ProviderRequest: req,
});
this.host.state["provider"] = provider;
return true;
});
return true;
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${msg("Authorization flow")}
?required=${true}
name="authorizationFlow"
>
<ak-flow-search-no-default
flowType=${FlowsInstancesListDesignationEnum.Authorization}
required
></ak-flow-search-no-default>
<p class="pf-c-form__helper-text">
${msg("Flow used when users access this application.")}
</p>
</ak-form-element-horizontal>
</form>`;
}
}

View file

@ -1,15 +0,0 @@
import "@goauthentik/elements/forms/HorizontalFormElement";
import { WizardFormPage } from "@goauthentik/elements/wizard/WizardFormPage";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { TemplateResult, html } from "lit";
@customElement("ak-application-wizard-type-oauth-implicit")
export class TypeOAuthImplicitApplicationWizardPage extends WizardFormPage {
sidebarLabel = () => msg("Method details");
render(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">some stuff idk</form> `;
}
}

View file

@ -1,64 +0,0 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { KeyUnknown } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { WizardFormPage } from "@goauthentik/elements/wizard/WizardFormPage";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { TemplateResult, html } from "lit";
import {
FlowDesignationEnum,
FlowsApi,
ProvidersApi,
ProxyProviderRequest,
} from "@goauthentik/api";
@customElement("ak-application-wizard-type-proxy")
export class TypeProxyApplicationWizardPage extends WizardFormPage {
sidebarLabel = () => msg("Proxy details");
nextDataCallback = async (data: KeyUnknown): Promise<boolean> => {
let name = this.host.state["name"] as string;
// Check if a provider with the name already exists
const providers = await new ProvidersApi(DEFAULT_CONFIG).providersAllList({
search: name,
});
if (providers.results.filter((provider) => provider.name == name)) {
name += "-1";
}
this.host.addActionBefore(msg("Create provider"), async (): Promise<boolean> => {
// Get all flows and default to the implicit authorization
const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
designation: FlowDesignationEnum.Authorization,
ordering: "slug",
});
const req: ProxyProviderRequest = {
name: name,
authorizationFlow: flows.results[0].pk,
externalHost: data.externalHost as string,
};
const provider = await new ProvidersApi(DEFAULT_CONFIG).providersProxyCreate({
proxyProviderRequest: req,
});
this.host.state["provider"] = provider;
return true;
});
return true;
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${msg("External domain")}
name="externalHost"
?required=${true}
>
<input type="text" value="" class="pf-c-form-control" required />
<p class="pf-c-form__helper-text">
${msg("External domain you will be accessing the domain from.")}
</p>
</ak-form-element-horizontal>
</form> `;
}
}

View file

@ -1,66 +0,0 @@
import "@goauthentik/elements/forms/HorizontalFormElement";
import { WizardPage } from "@goauthentik/elements/wizard/WizardPage";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { CSSResult, TemplateResult, html } from "lit";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { TypeCreate } from "@goauthentik/api";
@customElement("ak-application-wizard-type-saml")
export class TypeOAuthApplicationWizardPage extends WizardPage {
applicationTypes: TypeCreate[] = [
{
component: "ak-application-wizard-type-saml-import",
name: msg("Import SAML Metadata"),
description: msg(
"Import the metadata document of the applicaation you want to configure.",
),
modelName: "",
},
{
component: "ak-application-wizard-type-saml-config",
name: msg("Manual configuration"),
description: msg("Manually configure SAML"),
modelName: "",
},
];
static get styles(): CSSResult[] {
return [PFBase, PFButton, PFForm, PFRadio];
}
sidebarLabel = () => msg("Application type");
render(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
${this.applicationTypes.map((type) => {
return html`<div class="pf-c-radio">
<input
class="pf-c-radio__input"
type="radio"
name="type"
id=${type.component}
@change=${() => {
this.host.steps = [
"ak-application-wizard-initial",
"ak-application-wizard-type",
"ak-application-wizard-type-saml",
type.component,
];
this.host.state["saml-type"] = type.component;
this.host.isValid = true;
}}
/>
<label class="pf-c-radio__label" for=${type.component}>${type.name}</label>
<span class="pf-c-radio__description">${type.description}</span>
</div>`;
})}
</form> `;
}
}

View file

@ -1,57 +0,0 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { KeyUnknown } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { WizardFormPage } from "@goauthentik/elements/wizard/WizardFormPage";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { TemplateResult, html } from "lit";
import { FlowDesignationEnum, FlowsApi, ProvidersApi, SAMLProviderRequest } from "@goauthentik/api";
@customElement("ak-application-wizard-type-saml-config")
export class TypeSAMLApplicationWizardPage extends WizardFormPage {
sidebarLabel = () => msg("SAML details");
nextDataCallback = async (data: KeyUnknown): Promise<boolean> => {
let name = this.host.state["name"] as string;
// Check if a provider with the name already exists
const providers = await new ProvidersApi(DEFAULT_CONFIG).providersAllList({
search: name,
});
if (providers.results.filter((provider) => provider.name == name)) {
name += "-1";
}
this.host.addActionBefore(msg("Create provider"), async (): Promise<boolean> => {
// Get all flows and default to the implicit authorization
const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
designation: FlowDesignationEnum.Authorization,
ordering: "slug",
});
const req: SAMLProviderRequest = {
name: name,
authorizationFlow: flows.results[0].pk,
acsUrl: data.acsUrl as string,
};
const provider = await new ProvidersApi(DEFAULT_CONFIG).providersSamlCreate({
sAMLProviderRequest: req,
});
this.host.state["provider"] = provider;
return true;
});
return true;
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${msg("ACS URL")} name="acsUrl" ?required=${true}>
<input type="text" value="" class="pf-c-form-control" required />
<p class="pf-c-form__helper-text">
${msg(
"URL that authentik will redirect back to after successful authentication.",
)}
</p>
</ak-form-element-horizontal>
</form> `;
}
}

View file

@ -1,57 +0,0 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { KeyUnknown } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { WizardFormPage } from "@goauthentik/elements/wizard/WizardFormPage";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { TemplateResult, html } from "lit";
import {
FlowDesignationEnum,
FlowsApi,
ProvidersApi,
ProvidersSamlImportMetadataCreateRequest,
} from "@goauthentik/api";
@customElement("ak-application-wizard-type-saml-import")
export class TypeSAMLImportApplicationWizardPage extends WizardFormPage {
sidebarLabel = () => msg("Import SAML metadata");
nextDataCallback = async (data: KeyUnknown): Promise<boolean> => {
let name = this.host.state["name"] as string;
// Check if a provider with the name already exists
const providers = await new ProvidersApi(DEFAULT_CONFIG).providersAllList({
search: name,
});
if (providers.results.filter((provider) => provider.name == name)) {
name += "-1";
}
this.host.addActionBefore(msg("Create provider"), async (): Promise<boolean> => {
// Get all flows and default to the implicit authorization
const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
designation: FlowDesignationEnum.Authorization,
ordering: "slug",
});
const req: ProvidersSamlImportMetadataCreateRequest = {
name: name,
authorizationFlow: flows.results[0].slug,
file: data["metadata"] as Blob,
};
const provider = await new ProvidersApi(
DEFAULT_CONFIG,
).providersSamlImportMetadataCreate(req);
this.host.state["provider"] = provider;
return true;
});
return true;
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${msg("Metadata")} name="metadata">
<input type="file" value="" class="pf-c-form-control" />
</ak-form-element-horizontal>
</form> `;
}
}

View file

@ -0,0 +1,82 @@
import {
BackStep,
CancelWizard,
CloseWizard,
DisabledNextStep,
NextStep,
SubmitStep,
} from "@goauthentik/components/ak-wizard-main/commonWizardButtons";
import { html } from "lit";
import "./application/ak-application-wizard-application-details";
import "./auth-method-choice/ak-application-wizard-authentication-method-choice";
import "./commit/ak-application-wizard-commit-application";
import "./methods/ak-application-wizard-authentication-method";
import { ApplicationStep as ApplicationStepType } from "./types";
class ApplicationStep implements ApplicationStepType {
id = "application";
label = "Application Details";
disabled = false;
valid = false;
get buttons() {
return [this.valid ? NextStep : DisabledNextStep, CancelWizard];
}
render() {
return html`<ak-application-wizard-application-details></ak-application-wizard-application-details>`;
}
}
class ProviderMethodStep implements ApplicationStepType {
id = "provider-method";
label = "Provider Type";
disabled = false;
valid = false;
get buttons() {
return [BackStep, this.valid ? NextStep : DisabledNextStep, CancelWizard];
}
render() {
// prettier-ignore
return html`<ak-application-wizard-authentication-method-choice
></ak-application-wizard-authentication-method-choice> `;
}
}
class ProviderStepDetails implements ApplicationStepType {
id = "provider-details";
label = "Provider Configuration";
disabled = true;
valid = false;
get buttons() {
return [BackStep, this.valid ? SubmitStep : DisabledNextStep, CancelWizard];
}
render() {
return html`<ak-application-wizard-authentication-method></ak-application-wizard-authentication-method>`;
}
}
class SubmitApplicationStep implements ApplicationStepType {
id = "submit";
label = "Submit Application";
disabled = true;
valid = false;
get buttons() {
return this.valid ? [CloseWizard] : [BackStep, CancelWizard];
}
render() {
return html`<ak-application-wizard-commit-application></ak-application-wizard-commit-application>`;
}
}
export const newSteps = (): ApplicationStep[] => [
new ApplicationStep(),
new ProviderMethodStep(),
new ProviderStepDetails(),
new SubmitApplicationStep(),
];

View file

@ -0,0 +1,18 @@
import { consume } from "@lit-labs/context";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { state } from "@lit/reactive-element/decorators/state.js";
import { LitElement, html } from "lit";
import applicationWizardContext from "../ContextIdentity";
import type { ApplicationWizardState } from "../types";
@customElement("ak-application-context-display-for-test")
export class ApplicationContextDisplayForTest extends LitElement {
@consume({ context: applicationWizardContext, subscribe: true })
@state()
private wizard!: ApplicationWizardState;
render() {
return html`<div><pre>${JSON.stringify(this.wizard, null, 2)}</pre></div>`;
}
}

View file

@ -0,0 +1,54 @@
import { Meta } from "@storybook/web-components";
import { TemplateResult, html } from "lit";
import { ApplicationWizard } from "../ak-application-wizard";
import "../ak-application-wizard";
import { mockData } from "./mockData";
const metadata: Meta<ApplicationWizard> = {
title: "Elements / Application Wizard Implementation / Main",
component: "ak-application-wizard",
parameters: {
docs: {
description: {
component: "The first page of the application wizard",
},
},
mockData,
},
};
const LIGHT = "pf-t-light";
function injectTheme() {
setTimeout(() => {
if (!document.body.classList.contains(LIGHT)) {
document.body.classList.add(LIGHT);
}
});
}
export default metadata;
const container = (testItem: TemplateResult) => {
injectTheme();
return html` <div style="background: #fff; padding: 1.0rem;">
<style>
li {
display: block;
}
p {
margin-top: 1em;
}
</style>
${testItem}
</div>`;
};
export const MainPage = () => {
return container(html`
<ak-application-wizard></ak-application-wizard>
<hr />
<ak-application-context-display-for-test></ak-application-context-display-for-test>
`);
};

View file

@ -0,0 +1,62 @@
import {
dummyAuthenticationFlowsSearch,
dummyAuthorizationFlowsSearch,
dummyCoreGroupsSearch,
dummyCryptoCertsSearch,
dummyHasJwks,
dummyPropertyMappings,
dummyProviderTypesList,
dummySAMLProviderMappings,
} from "./samples";
export const mockData = [
{
url: "/api/v3/providers/all/types/",
method: "GET",
status: 200,
response: dummyProviderTypesList,
},
{
url: "/api/v3/core/groups/?ordering=name",
method: "GET",
status: 200,
response: dummyCoreGroupsSearch,
},
{
url: "/api/v3/crypto/certificatekeypairs/?has_key=true&include_details=false&ordering=name",
method: "GET",
status: 200,
response: dummyCryptoCertsSearch,
},
{
url: "/api/v3/flows/instances/?designation=authentication&ordering=slug",
method: "GET",
status: 200,
response: dummyAuthenticationFlowsSearch,
},
{
url: "/api/v3/flows/instances/?designation=authorization&ordering=slug",
method: "GET",
status: 200,
response: dummyAuthorizationFlowsSearch,
},
{
url: "/api/v3/propertymappings/scope/?ordering=scope_name",
method: "GET",
status: 200,
response: dummyPropertyMappings,
},
{
url: "/api/v3/sources/oauth/?has_jwks=true&ordering=name",
method: "GET",
status: 200,
response: dummyHasJwks,
},
{
url: "/api/v3/propertymappings/saml/?ordering=saml_name",
method: "GET",
status: 200,
response: dummySAMLProviderMappings,
},
];

View file

@ -0,0 +1,375 @@
export const dummyCryptoCertsSearch = {
pagination: {
next: 0,
previous: 0,
count: 1,
current: 1,
total_pages: 1,
start_index: 1,
end_index: 1,
},
results: [
{
pk: "63efd1b8-6c39-4f65-8157-9a406cb37447",
name: "authentik Self-signed Certificate",
fingerprint_sha256: null,
fingerprint_sha1: null,
cert_expiry: null,
cert_subject: null,
private_key_available: true,
private_key_type: null,
certificate_download_url:
"/api/v3/crypto/certificatekeypairs/63efd1b8-6c39-4f65-8157-9a406cb37447/view_certificate/?download",
private_key_download_url:
"/api/v3/crypto/certificatekeypairs/63efd1b8-6c39-4f65-8157-9a406cb37447/view_private_key/?download",
managed: null,
},
],
};
export const dummyAuthenticationFlowsSearch = {
pagination: {
next: 0,
previous: 0,
count: 2,
current: 1,
total_pages: 1,
start_index: 1,
end_index: 2,
},
results: [
{
pk: "2594b1a0-f234-4965-8b93-a8631a55bd5c",
policybindingmodel_ptr_id: "0bc529a6-dcd0-4ba8-8fef-5702348832f9",
name: "Welcome to authentik!",
slug: "default-authentication-flow",
title: "Welcome to authentik!",
designation: "authentication",
background: "/static/dist/assets/images/flow_background.jpg",
stages: [
"bad9fbce-fb86-4ba4-8124-e7a1d8c147f3",
"1da1f272-a76e-4112-be95-f02421fca1d4",
"945cd956-6670-4dfa-ab3a-2a72dd3051a7",
"0fc1fc5c-b928-4d99-a892-9ae48de089f5",
],
policies: [],
cache_count: 0,
policy_engine_mode: "any",
compatibility_mode: false,
export_url: "/api/v3/flows/instances/default-authentication-flow/export/",
layout: "stacked",
denied_action: "message_continue",
authentication: "none",
},
{
pk: "3526dbd1-b50e-4553-bada-fbe7b3c2f660",
policybindingmodel_ptr_id: "cde67954-b78a-4fe9-830e-c2aba07a724a",
name: "Welcome to authentik!",
slug: "default-source-authentication",
title: "Welcome to authentik!",
designation: "authentication",
background: "/static/dist/assets/images/flow_background.jpg",
stages: ["3713b252-cee3-4acb-a02f-083f26459fff"],
policies: ["f42a4c7f-6586-4b14-9325-a832127ba295"],
cache_count: 0,
policy_engine_mode: "any",
compatibility_mode: false,
export_url: "/api/v3/flows/instances/default-source-authentication/export/",
layout: "stacked",
denied_action: "message_continue",
authentication: "require_unauthenticated",
},
],
};
export const dummyAuthorizationFlowsSearch = {
pagination: {
next: 0,
previous: 0,
count: 2,
current: 1,
total_pages: 1,
start_index: 1,
end_index: 2,
},
results: [
{
pk: "9e01f011-8b3f-43d6-bedf-c29be5f3a428",
policybindingmodel_ptr_id: "14179ef8-2726-4027-9e2f-dc99185199bf",
name: "Authorize Application",
slug: "default-provider-authorization-explicit-consent",
title: "Redirecting to %(app)s",
designation: "authorization",
background: "/static/dist/assets/images/flow_background.jpg",
stages: ["ed5f015f-82b9-450f-addf-1e9d21d8dda3"],
policies: [],
cache_count: 0,
policy_engine_mode: "any",
compatibility_mode: false,
export_url:
"/api/v3/flows/instances/default-provider-authorization-explicit-consent/export/",
layout: "stacked",
denied_action: "message_continue",
authentication: "require_authenticated",
},
{
pk: "06f11ee3-cbe3-456d-81df-fae4c0a62951",
policybindingmodel_ptr_id: "686e6539-8b9f-473e-9f54-e05cc207dd2a",
name: "Authorize Application",
slug: "default-provider-authorization-implicit-consent",
title: "Redirecting to %(app)s",
designation: "authorization",
background: "/static/dist/assets/images/flow_background.jpg",
stages: [],
policies: [],
cache_count: 0,
policy_engine_mode: "any",
compatibility_mode: false,
export_url:
"/api/v3/flows/instances/default-provider-authorization-implicit-consent/export/",
layout: "stacked",
denied_action: "message_continue",
authentication: "require_authenticated",
},
],
};
export const dummyCoreGroupsSearch = {
pagination: {
next: 0,
previous: 0,
count: 1,
current: 1,
total_pages: 1,
start_index: 1,
end_index: 1,
},
results: [
{
pk: "67543d37-0ee2-4a4c-b020-9e735a8b5178",
num_pk: 13734,
name: "authentik Admins",
is_superuser: true,
parent: null,
users: [1],
attributes: {},
users_obj: [
{
pk: 1,
username: "akadmin",
name: "authentik Default Admin",
is_active: true,
last_login: "2023-07-03T16:08:11.196942Z",
email: "ken@goauthentik.io",
attributes: {
settings: {
locale: "en",
},
},
uid: "6dedc98b3fdd0f9afdc705e9d577d61127d89f1d91ea2f90f0b9a353615fb8f2",
},
],
},
],
};
export const dummyPropertyMappings = {
pagination: {
next: 0,
previous: 0,
count: 4,
current: 1,
total_pages: 1,
start_index: 1,
end_index: 4,
},
results: [
{
pk: "30d87af7-9d9d-4292-873e-a52145ba4bcb",
managed: "goauthentik.io/providers/proxy/scope-proxy",
name: "authentik default OAuth Mapping: Proxy outpost",
expression:
'# This mapping is used by the authentik proxy. It passes extra user attributes,\n# which are used for example for the HTTP-Basic Authentication mapping.\nreturn {\n "ak_proxy": {\n "user_attributes": request.user.group_attributes(request),\n "is_superuser": request.user.is_superuser,\n }\n}',
component: "ak-property-mapping-scope-form",
verbose_name: "Scope Mapping",
verbose_name_plural: "Scope Mappings",
meta_model_name: "authentik_providers_oauth2.scopemapping",
scope_name: "ak_proxy",
description: "authentik Proxy - User information",
},
{
pk: "3e3751ed-a24c-4f47-a051-e2e05b5cd306",
managed: "goauthentik.io/providers/oauth2/scope-email",
name: "authentik default OAuth Mapping: OpenID 'email'",
expression: 'return {\n "email": request.user.email,\n "email_verified": True\n}',
component: "ak-property-mapping-scope-form",
verbose_name: "Scope Mapping",
verbose_name_plural: "Scope Mappings",
meta_model_name: "authentik_providers_oauth2.scopemapping",
scope_name: "email",
description: "Email address",
},
{
pk: "81c5e330-d8a0-45cd-9cad-e6a49a9c428f",
managed: "goauthentik.io/providers/oauth2/scope-openid",
name: "authentik default OAuth Mapping: OpenID 'openid'",
expression:
"# This scope is required by the OpenID-spec, and must as such exist in authentik.\n# The scope by itself does not grant any information\nreturn {}",
component: "ak-property-mapping-scope-form",
verbose_name: "Scope Mapping",
verbose_name_plural: "Scope Mappings",
meta_model_name: "authentik_providers_oauth2.scopemapping",
scope_name: "openid",
description: "",
},
{
pk: "7ad9cd6f-bcc8-425d-b7c2-c7c4592a1b36",
managed: "goauthentik.io/providers/oauth2/scope-profile",
name: "authentik default OAuth Mapping: OpenID 'profile'",
expression:
'return {\n # Because authentik only saves the user\'s full name, and has no concept of first and last names,\n # the full name is used as given name.\n # You can override this behaviour in custom mappings, i.e. `request.user.name.split(" ")`\n "name": request.user.name,\n "given_name": request.user.name,\n "preferred_username": request.user.username,\n "nickname": request.user.username,\n # groups is not part of the official userinfo schema, but is a quasi-standard\n "groups": [group.name for group in request.user.ak_groups.all()],\n}',
component: "ak-property-mapping-scope-form",
verbose_name: "Scope Mapping",
verbose_name_plural: "Scope Mappings",
meta_model_name: "authentik_providers_oauth2.scopemapping",
scope_name: "profile",
description: "General Profile Information",
},
],
};
export const dummyHasJwks = {
pagination: {
next: 0,
previous: 0,
count: 0,
current: 1,
total_pages: 1,
start_index: 0,
end_index: 0,
},
results: [],
};
export const dummySAMLProviderMappings = {
pagination: {
next: 0,
previous: 0,
count: 7,
current: 1,
total_pages: 1,
start_index: 1,
end_index: 7,
},
results: [
{
pk: "9f1f23b7-1956-4daa-b08b-338cab9b3953",
managed: "goauthentik.io/providers/saml/uid",
name: "authentik default SAML Mapping: User ID",
expression: "return request.user.pk",
component: "ak-property-mapping-saml-form",
verbose_name: "SAML Property Mapping",
verbose_name_plural: "SAML Property Mappings",
meta_model_name: "authentik_providers_saml.samlpropertymapping",
saml_name: "http://schemas.goauthentik.io/2021/02/saml/uid",
friendly_name: null,
},
{
pk: "801b6328-bb0b-4ec6-b52c-f3dc7bb6ec7f",
managed: "goauthentik.io/providers/saml/username",
name: "authentik default SAML Mapping: Username",
expression: "return request.user.username",
component: "ak-property-mapping-saml-form",
verbose_name: "SAML Property Mapping",
verbose_name_plural: "SAML Property Mappings",
meta_model_name: "authentik_providers_saml.samlpropertymapping",
saml_name: "http://schemas.goauthentik.io/2021/02/saml/username",
friendly_name: null,
},
{
pk: "27c4d370-658d-4acf-9f61-cfa6dd020b11",
managed: "goauthentik.io/providers/saml/ms-windowsaccountname",
name: "authentik default SAML Mapping: WindowsAccountname (Username)",
expression: "return request.user.username",
component: "ak-property-mapping-saml-form",
verbose_name: "SAML Property Mapping",
verbose_name_plural: "SAML Property Mappings",
meta_model_name: "authentik_providers_saml.samlpropertymapping",
saml_name: "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname",
friendly_name: null,
},
{
pk: "757b185b-1c21-42b4-a2ee-04d6f7b655b3",
managed: "goauthentik.io/providers/saml/groups",
name: "authentik default SAML Mapping: Groups",
expression: "for group in request.user.ak_groups.all():\n yield group.name",
component: "ak-property-mapping-saml-form",
verbose_name: "SAML Property Mapping",
verbose_name_plural: "SAML Property Mappings",
meta_model_name: "authentik_providers_saml.samlpropertymapping",
saml_name: "http://schemas.xmlsoap.org/claims/Group",
friendly_name: null,
},
{
pk: "de67cee7-7c56-4c1d-9466-9ad0e0105092",
managed: "goauthentik.io/providers/saml/email",
name: "authentik default SAML Mapping: Email",
expression: "return request.user.email",
component: "ak-property-mapping-saml-form",
verbose_name: "SAML Property Mapping",
verbose_name_plural: "SAML Property Mappings",
meta_model_name: "authentik_providers_saml.samlpropertymapping",
saml_name: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
friendly_name: null,
},
{
pk: "42a936a5-11a9-4442-8748-ec27a8ab9546",
managed: "goauthentik.io/providers/saml/name",
name: "authentik default SAML Mapping: Name",
expression: "return request.user.name",
component: "ak-property-mapping-saml-form",
verbose_name: "SAML Property Mapping",
verbose_name_plural: "SAML Property Mappings",
meta_model_name: "authentik_providers_saml.samlpropertymapping",
saml_name: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
friendly_name: null,
},
{
pk: "06bee8f0-e5b4-4ce8-959a-308ba0769917",
managed: "goauthentik.io/providers/saml/upn",
name: "authentik default SAML Mapping: UPN",
expression: "return request.user.attributes.get('upn', request.user.email)",
component: "ak-property-mapping-saml-form",
verbose_name: "SAML Property Mapping",
verbose_name_plural: "SAML Property Mappings",
meta_model_name: "authentik_providers_saml.samlpropertymapping",
saml_name: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn",
friendly_name: null,
},
],
};
// prettier-ignore
export const dummyProviderTypesList = [
["LDAP Provider", "ldapprovider",
"Allow applications to authenticate against authentik's users using LDAP.",
],
["OAuth2/OpenID Provider", "oauth2provider",
"OAuth2 Provider for generic OAuth and OpenID Connect Applications.",
],
["Proxy Provider", "proxyprovider",
"Protect applications that don't support any of the other\n Protocols by using a Reverse-Proxy.",
],
["Radius Provider", "radiusprovider",
"Allow applications to authenticate against authentik's users using Radius.",
],
["SAML Provider", "samlprovider",
"SAML 2.0 Endpoint for applications which support SAML.",
],
["SCIM Provider", "scimprovider",
"SCIM 2.0 provider to create users and groups in external applications",
],
["SAML Provider from Metadata", "",
"Create a SAML Provider by importing its Metadata.",
],
].map(([name, model_name, description]) => ({ name, description, model_name }));

View file

@ -0,0 +1,39 @@
import { type WizardStep } from "@goauthentik/components/ak-wizard-main/types";
import {
type ApplicationRequest,
type LDAPProviderRequest,
type OAuth2ProviderRequest,
type ProvidersSamlImportMetadataCreateRequest,
type ProxyProviderRequest,
type RadiusProviderRequest,
type SAMLProviderRequest,
type SCIMProviderRequest,
} from "@goauthentik/api";
export type OneOfProvider =
| Partial<SCIMProviderRequest>
| Partial<SAMLProviderRequest>
| Partial<ProvidersSamlImportMetadataCreateRequest>
| Partial<RadiusProviderRequest>
| Partial<ProxyProviderRequest>
| Partial<OAuth2ProviderRequest>
| Partial<LDAPProviderRequest>;
export interface ApplicationWizardState {
providerModel: string;
app: Partial<ApplicationRequest>;
provider: OneOfProvider;
}
type StatusType = "invalid" | "valid" | "submitted" | "failed";
export type ApplicationWizardStateUpdate = {
update?: Partial<ApplicationWizardState>;
status?: StatusType;
};
export type ApplicationStep = WizardStep & {
id: string;
valid: boolean;
};

View file

@ -1,3 +1,4 @@
import "@goauthentik/admin/applications/ApplicationWizardHint";
import "@goauthentik/admin/providers/ProviderWizard";
import "@goauthentik/admin/providers/ldap/LDAPProviderForm";
import "@goauthentik/admin/providers/oauth2/OAuth2ProviderForm";
@ -60,6 +61,10 @@ export class ProviderListPage extends TablePage<Provider> {
];
}
renderSectionBefore(): TemplateResult {
return html`<ak-application-wizard-hint></ak-application-wizard-hint>`;
}
renderToolbarSelected(): TemplateResult {
const disabled = this.selectedElements.length < 1;
return html`<ak-forms-delete-bulk

120
web/src/common/merge.ts Normal file
View file

@ -0,0 +1,120 @@
/** Taken from: https://github.com/zellwk/javascript/tree/master
*
* We have added some typescript annotations, but this is such a rich feature with deep nesting
* we'll just have to watch it closely for any issues. So far there don't seem to be any.
*
*/
function objectType<T>(value: T) {
return Object.prototype.toString.call(value);
}
// Creates a deep clone for each value
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function cloneDescriptorValue(value: any) {
// Arrays
if (objectType(value) === "[object Array]") {
const array = [];
for (let v of value) {
v = cloneDescriptorValue(v);
array.push(v);
}
return array;
}
// Objects
if (objectType(value) === "[object Object]") {
const obj = {};
const props = Object.keys(value);
for (const prop of props) {
const descriptor = Object.getOwnPropertyDescriptor(value, prop);
if (!descriptor) {
continue;
}
if (descriptor.value) {
descriptor.value = cloneDescriptorValue(descriptor.value);
}
Object.defineProperty(obj, prop, descriptor);
}
return obj;
}
// Other Types of Objects
if (objectType(value) === "[object Date]") {
return new Date(value.getTime());
}
if (objectType(value) === "[object Map]") {
const map = new Map();
for (const entry of value) {
map.set(entry[0], cloneDescriptorValue(entry[1]));
}
return map;
}
if (objectType(value) === "[object Set]") {
const set = new Set();
for (const entry of value.entries()) {
set.add(cloneDescriptorValue(entry[0]));
}
return set;
}
// Types we don't need to clone or cannot clone.
// Examples:
// - Primitives don't need to clone
// - Functions cannot clone
return value;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function _merge(output: Record<string, any>, input: Record<string, any>) {
const props = Object.keys(input);
for (const prop of props) {
// Prevents Prototype Pollution
if (prop === "__proto__") continue;
const descriptor = Object.getOwnPropertyDescriptor(input, prop);
if (!descriptor) {
continue;
}
const value = descriptor.value;
if (value) descriptor.value = cloneDescriptorValue(value);
// If don't have prop => Define property
// [ken@goauthentik] Using `hasOwn` is preferable over
// the basic identity test, according to Typescript.
if (!Object.hasOwn(output, prop)) {
Object.defineProperty(output, prop, descriptor);
continue;
}
// If have prop, but type is not object => Overwrite by redefining property
if (typeof output[prop] !== "object") {
Object.defineProperty(output, prop, descriptor);
continue;
}
// If have prop, but type is Object => Concat the arrays together.
if (objectType(descriptor.value) === "[object Array]") {
output[prop] = output[prop].concat(descriptor.value);
continue;
}
// If have prop, but type is Object => Merge.
_merge(output[prop], descriptor.value);
}
}
export function merge(...sources: Array<object>) {
const result = {};
for (const source of sources) {
_merge(result, source);
}
return result;
}
export default merge;

View file

@ -27,18 +27,27 @@ export class ShowHintController implements ReactiveController {
constructor(host: ShowHintControllerHost, hintToken: string) {
(this.host = host).addController(this);
this.hintToken = hintToken;
this.hideTheHint = this.hideTheHint.bind(this);
this.hide = this.hide.bind(this);
this.show = this.show.bind(this);
}
hideTheHint() {
setTheHint(state: boolean = false) {
window?.localStorage.setItem(
LOCALSTORAGE_AUTHENTIK_KEY,
JSON.stringify({
...getCurrentStorageValue(),
[this.hintToken]: false,
[this.hintToken]: state,
}),
);
this.host.showHint = false;
this.host.showHint = state;
}
hide() {
this.setTheHint(false);
}
show() {
this.setTheHint(true);
}
hostConnected() {
@ -54,7 +63,7 @@ export class ShowHintController implements ReactiveController {
render() {
return html`<ak-hint-footer
><div style="text-align: right">
<input type="checkbox" @input=${this.hideTheHint} />${msg(
<input type="checkbox" @input=${this.hide} />${msg(
"Don't show this message again.",
)}
</div></ak-hint-footer

View file

@ -0,0 +1,121 @@
import "@goauthentik/app/components/ak-wizard-main/ak-wizard-frame";
import { AKElement } from "@goauthentik/elements/Base";
import { msg } from "@lit/localize";
import { ReactiveControllerHost, html } from "lit";
import { state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { Ref, createRef, ref } from "lit/directives/ref.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { AkWizardController } from "./AkWizardController";
import { AkWizardFrame } from "./ak-wizard-frame";
import { type WizardStep, type WizardStepLabel } from "./types";
/**
* Abstract parent class for wizards. This Class activates the Controller, provides the default
* renderer and handleNav() functions, and organizes the various texts used to describe a Modal
* Wizard's interaction: its prompt, header, and description.
*/
export class AkWizard<D, Step extends WizardStep = WizardStep>
extends AKElement
implements ReactiveControllerHost
{
// prettier-ignore
static get styles() { return [PFBase, PFButton]; }
@state()
steps: Step[] = [];
@state()
currentStep = 0;
/**
* A reference to the frame. Since the frame implements and inherits from ModalButton,
* you will need either a reference to or query to the frame in order to call
* `.close()` on it.
*/
frame: Ref<AkWizardFrame> = createRef();
get step() {
return this.steps[this.currentStep];
}
prompt = msg("Create");
header: string;
description?: string;
wizard: AkWizardController<D>;
constructor(prompt: string, header: string, description?: string) {
super();
this.header = header;
this.prompt = prompt;
this.description = description;
this.wizard = new AkWizardController(this);
}
/**
* Derive the labels used by the frame's Breadcrumbs display.
*/
get stepLabels(): WizardStepLabel[] {
let disabled = false;
return this.steps.map((step, index) => {
disabled = disabled || step.disabled;
return {
label: step.label,
active: index === this.currentStep,
index,
disabled,
};
});
}
/**
* You should still consider overriding this if you need to consider details like "Is the step
* requested valid?"
*/
handleNav(stepId: number | undefined) {
if (stepId === undefined || this.steps[stepId] === undefined) {
throw new Error(`Attempt to navigate to undefined step: ${stepId}`);
}
this.currentStep = stepId;
this.requestUpdate();
}
close() {
throw new Error("This function must be overridden in the child class.");
}
/**
* This is where all the business logic and special cases go. The Wizard Controller intercepts
* updates tagged `ak-wizard-update` and forwards the event content here. Business logic about
* "is the current step valid?" and "should the Next button be made enabled" are controlled
* here. (Any step implementing WizardStep can do it anyhow it pleases, putting "is the current
* form valid" and so forth into the step object itself.)
*/
handleUpdate(_detail: D) {
throw new Error("This function must be overridden in the child class.");
}
render() {
return html`
<ak-wizard-frame
${ref(this.frame)}
header=${this.header}
description=${ifDefined(this.description)}
prompt=${this.prompt}
.buttons=${this.step.buttons}
.stepLabels=${this.stepLabels}
.form=${this.step.render.bind(this.step)}
>
<button slot="trigger" class="pf-c-button pf-m-primary">${this.prompt}</button>
</ak-wizard-frame>
`;
}
}

View file

@ -0,0 +1,104 @@
import { type ReactiveController } from "lit";
import { type AkWizard, type WizardNavCommand } from "./types";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const isCustomEvent = (v: any): v is CustomEvent =>
v instanceof CustomEvent && "detail" in v;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const isNavEvent = (v: any): v is CustomEvent<WizardNavCommand> =>
isCustomEvent(v) && "command" in v.detail;
/**
* AkWizardController
*
* A ReactiveController that plugs into any wizard and provides a somewhat more convenient API for
* interacting with that wizard. It expects three different events from the wizard frame, each of
* which has a corresponding method that then forwards the necessary information to the host:
*
* - nav: A request to navigate to different step. Calls the host's `handleNav()` with the requested
step number.
* - update: A request to update the content of the current step. Forwarded to the host's
* `handleUpdate()` method.
* - close: A request to end the wizard interaction. Forwarded to the host's `close()` method.
*
*/
export class AkWizardController<Data> implements ReactiveController {
private host: AkWizard<Data>;
constructor(host: AkWizard<Data>) {
this.host = host;
this.handleNavRequest = this.handleNavRequest.bind(this);
this.handleUpdateRequest = this.handleUpdateRequest.bind(this);
host.addController(this);
}
get maxStep() {
return this.host.steps.length - 1;
}
get nextStep() {
return this.host.currentStep < this.maxStep ? this.host.currentStep + 1 : undefined;
}
get backStep() {
return this.host.currentStep > 0 ? this.host.currentStep - 1 : undefined;
}
get step() {
return this.host.steps[this.host.currentStep];
}
hostConnected() {
this.host.addEventListener("ak-wizard-nav", this.handleNavRequest);
this.host.addEventListener("ak-wizard-update", this.handleUpdateRequest);
this.host.addEventListener("ak-wizard-closed", this.handleCloseRequest);
}
hostDisconnected() {
this.host.removeEventListener("ak-wizard-nav", this.handleNavRequest);
this.host.removeEventListener("ak-wizard-update", this.handleUpdateRequest);
this.host.removeEventListener("ak-wizard-closed", this.handleCloseRequest);
}
handleNavRequest(event: Event) {
if (!isNavEvent(event)) {
throw new Error(`Unexpected event received by nav handler: ${event}`);
}
if (event.detail.command === "close") {
this.host.close();
return;
}
const navigate = (): number | undefined => {
switch (event.detail.command) {
case "next":
return this.nextStep;
case "back":
return this.backStep;
case "goto":
return event.detail.step;
default:
throw new Error(
`Unrecognized command passed to ak-wizard-controller:handleNavRequest: ${event.detail.command}`,
);
}
};
this.host.handleNav(navigate());
}
handleUpdateRequest(event: Event) {
if (!isCustomEvent(event)) {
throw new Error(`Unexpected event received by nav handler: ${event}`);
}
this.host.handleUpdate(event.detail);
}
handleCloseRequest() {
this.host.close();
}
}

View file

@ -0,0 +1,201 @@
import { ModalButton } from "@goauthentik/elements/buttons/ModalButton";
import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter";
import { msg } from "@lit/localize";
import { customElement, property, query } from "@lit/reactive-element/decorators.js";
import { TemplateResult, html, nothing } from "lit";
import { classMap } from "lit/directives/class-map.js";
import { map } from "lit/directives/map.js";
import PFWizard from "@patternfly/patternfly/components/Wizard/wizard.css";
import { type WizardButton, WizardStepLabel } from "./types";
/**
* AKWizardFrame is the main container for displaying Wizard pages.
*
* AKWizardFrame is one component of a Wizard development environment. It provides the header,
* titled navigation sidebar, and bottom row button bar. It takes its cues about what to render from
* two data structure, `this.steps: WizardStep[]`, which lists all the current steps *in order* and
* doesn't care otherwise about their structure, and `this.currentStep: WizardStep` which must be a
* _reference_ to a member of `this.steps`.
*
* @element ak-wizard-frame
*
* @slot - Where the form itself should go
*
* @fires ak-wizard-nav - Tell the orchestrator what page the user wishes to move to.
*
*/
@customElement("ak-wizard-frame")
export class AkWizardFrame extends CustomEmitterElement(ModalButton) {
static get styles() {
return [...super.styles, PFWizard];
}
/**
* The text for the title of the wizard
*/
@property()
header?: string;
/**
* The text for a descriptive subtitle for the wizard
*/
@property()
description?: string;
/**
* The labels for all current steps, including their availability
*/
@property({ attribute: false, type: Array })
stepLabels!: WizardStepLabel[];
/**
* What buttons to Show
*/
@property({ attribute: false, type: Array })
buttons: WizardButton[] = [];
/**
* Show the [Cancel] icon and offer the [Cancel] button
*/
@property({ type: Boolean, attribute: "can-cancel" })
canCancel = false;
/**
* The form renderer, passed as a function
*/
@property({ type: Object })
form!: () => TemplateResult;
@query("#main-content *:first-child")
content!: HTMLElement;
constructor() {
super();
this.renderButtons = this.renderButtons.bind(this);
}
renderModalInner() {
// prettier-ignore
return html`<div class="pf-c-wizard">
${this.renderHeader()}
<div class="pf-c-wizard__outer-wrap">
<div class="pf-c-wizard__inner-wrap">
${this.renderNavigation()}
${this.renderMainSection()}
</div>
${this.renderFooter()}
</div>
</div>`;
}
renderHeader() {
return html`<div class="pf-c-wizard__header">
${this.canCancel ? this.renderHeaderCancelIcon() : nothing}
<h1 class="pf-c-title pf-m-3xl pf-c-wizard__title">${this.header}</h1>
<p class="pf-c-wizard__description">${this.description}</p>
</div>`;
}
renderHeaderCancelIcon() {
return html`<button
class="pf-c-button pf-m-plain pf-c-wizard__close"
type="button"
aria-label="${msg("Close")}"
@click=${() => this.dispatchCustomEvent("ak-wizard-nav", { command: "close" })}
>
<i class="fas fa-times" aria-hidden="true"></i>
</button>`;
}
renderNavigation() {
return html`<nav class="pf-c-wizard__nav">
<ol class="pf-c-wizard__nav-list">
${this.stepLabels.map((step) => {
return this.renderNavigationStep(step);
})}
</ol>
</nav>`;
}
renderNavigationStep(step: WizardStepLabel) {
const buttonClasses = {
"pf-c-wizard__nav-link": true,
"pf-m-current": step.active,
};
return html`
<li class="pf-c-wizard__nav-item">
<button
class=${classMap(buttonClasses)}
?disabled=${step.disabled}
@click=${() =>
this.dispatchCustomEvent("ak-wizard-nav", {
command: "goto",
step: step.index,
})}
>
${step.label}
</button>
</li>
`;
}
// This is where the panel is shown. We expect the panel to get its information from an
// independent context.
renderMainSection() {
return html`<main class="pf-c-wizard__main">
<div id="main-content" class="pf-c-wizard__main-body">${this.form()}</div>
</main>`;
}
renderFooter() {
return html`
<footer class="pf-c-wizard__footer">${map(this.buttons, this.renderButtons)}</footer>
`;
}
renderButtons([label, command]: WizardButton) {
switch (command.command) {
case "next":
return this.renderButton(label, "pf-m-primary", command.command);
case "back":
return this.renderButton(label, "pf-m-secondary", command.command);
case "close":
return this.renderLink(label, "pf-m-link");
default:
throw new Error(`Button type not understood: ${command} for ${label}`);
}
}
renderButton(label: string, classname: string, command: string) {
const buttonClasses = { "pf-c-button": true, [classname]: true };
return html`<button
class=${classMap(buttonClasses)}
type="button"
@click=${() => {
this.dispatchCustomEvent("ak-wizard-nav", { command });
}}
>
${label}
</button>`;
}
renderLink(label: string, classname: string) {
const buttonClasses = { "pf-c-button": true, [classname]: true };
return html`<div class="pf-c-wizard__footer-cancel">
<button
class=${classMap(buttonClasses)}
type="button"
@click=${() => this.dispatchCustomEvent("ak-wizard-nav", { command: "close" })}
>
${label}
</button>
</div>`;
}
}
export default AkWizardFrame;

View file

@ -0,0 +1,15 @@
import { msg } from "@lit/localize";
import { WizardButton } from "./types";
export const NextStep: WizardButton = [msg("Next"), { command: "next" }];
export const BackStep: WizardButton = [msg("Back"), { command: "back" }];
export const SubmitStep: WizardButton = [msg("Submit"), { command: "next" }];
export const CancelWizard: WizardButton = [msg("Cancel"), { command: "close" }];
export const CloseWizard: WizardButton = [msg("Close"), { command: "close" }];
export const DisabledNextStep: WizardButton = [msg("Next"), { command: "next" }, true];

View file

@ -0,0 +1,47 @@
import type { WizardStep } from "@goauthentik/components/ak-wizard-main/types";
import { msg } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { html } from "lit";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { AkWizard } from "../AkWizard";
import { BackStep, CancelWizard, CloseWizard, NextStep } from "../commonWizardButtons";
type WizardStateUpdate = {
message: string;
};
const dummySteps: WizardStep[] = [
{
label: "Test Step1",
render: () => html`<h2>This space intentionally left blank today</h2>`,
disabled: false,
buttons: [NextStep, CancelWizard],
},
{
label: "Test Step 2",
render: () => html`<h2>This space also intentionally left blank</h2>`,
disabled: false,
buttons: [BackStep, CloseWizard],
},
];
@customElement("ak-demo-wizard")
export class ApplicationWizard extends AkWizard<WizardStateUpdate, WizardStep> {
static get styles() {
return [PFBase, PFButton, PFRadio];
}
constructor() {
super(msg("Open Wizard"), msg("Demo Wizard"), msg("Run the demo wizard"));
this.steps = [...dummySteps];
}
close() {
this.frame.value!.open = false;
}
}

View file

@ -0,0 +1,40 @@
import type { WizardStep } from "@goauthentik/components/ak-wizard-main/types";
import "@goauthentik/elements/messages/MessageContainer";
import { Meta } from "@storybook/web-components";
import { TemplateResult, html } from "lit";
import { AkWizard } from "../AkWizard";
import "./ak-demo-wizard";
const metadata: Meta<AkWizard<string, WizardStep>> = {
title: "Components / Wizard / Basic",
component: "ak-wizard-main",
parameters: {
docs: {
description: {
component: "A container for our wizard.",
},
},
},
};
export default metadata;
const container = (testItem: TemplateResult) =>
html` <div style="background: #fff; padding: 2em">
<style>
li {
display: block;
}
p {
margin-top: 1em;
}
</style>
<ak-message-container></ak-message-container>
${testItem}
</div>`;
export const OnePageWizard = () => {
return container(html` <ak-demo-wizard></ak-demo-wizard>`);
};

View file

@ -0,0 +1,79 @@
import { type LitElement, type ReactiveControllerHost, type TemplateResult } from "lit";
/** These are the navigation commands that the frame will send up to the controller. In the
* accompanying file, `./commonWizardButtons.ts`, you'll find a variety of Next, Back, Close,
* Cancel, and Submit buttons that can be used to send these, but these commands are also
* used by the breadcrumbs to hop around the wizard (if the wizard client so chooses to allow),
*/
export type WizardNavCommand =
| { command: "next" }
| { command: "back" }
| { command: "close" }
| { command: "goto"; step: number };
/**
* The pattern for buttons being passed to the wizard. See `./commonWizardButtons.ts` for
* example implementations. The details are: Label, Command, and Disabled.
*/
export type WizardButton = [string, WizardNavCommand, boolean?];
/**
* Objects of this type are produced by the Controller, and are used in the Breadcrumbs to
* indicate the name of the step, whether or not it is the current step ("active"), and
* whether or not it is disabled. It is up to WizardClients to ensure that a step is
* not both "active" and "disabled".
*/
export type WizardStepLabel = {
label: string;
index: number;
active: boolean;
disabled: boolean;
};
type LitControllerHost = ReactiveControllerHost & LitElement;
export interface AkWizard<D> extends LitControllerHost {
// Every wizard must provide a list of the steps to show. This list can change, but if it does,
// note that the *first* page must never change, and it's the responsibility of the developer to
// ensure that if the list changes that the currentStep points to the right place.
steps: WizardStep[];
// The index of the current step;
currentStep: number;
// An accessor to the current step;
step: WizardStep;
// Handle pressing the "close," "cancel," or "done" buttons.
close: () => void;
// When a navigation event such as "next," "back," or "go to" (from the breadcrumbs) occurs.
handleNav: (_1: number | undefined) => void;
// When a notification that the data on the live form has changed.
handleUpdate: (_1: D) => void;
}
export interface WizardStep {
// The name of the step, as shown in the navigation.
label: string;
// A function which returns the html for rendering the actual content of the step, its form and
// such.
render: () => TemplateResult;
// A collection of buttons, in render order, that are to be shown in the button bar. If you can,
// always lead with the [Back] button and ensure it's in the same place every time. The
// controller's current behavior is to call the host's `handleNav()` command with the index of
// the requested step, or undefined if the command is nonsensical.
buttons: WizardButton[];
// If this step is "disabled," the prior step's next button will be disabled.
disabled: boolean;
}
export interface WizardPanel extends HTMLElement {
validator?: () => boolean;
}

View file

@ -1404,10 +1404,6 @@
<source>Slug</source>
<target>Slug</target>
</trans-unit>
<trans-unit id="seb8407fa6607c683">
<source>Internal application name, used in URLs.</source>
<target>Interner Applikationsname, wird in URLs verwendet.</target>
</trans-unit>
<trans-unit id="sdae55084f6cb2662">
<source>Optionally enter a group name. Applications with identical groups are shown grouped together.</source>
<target>Geben Sie optional einen Gruppennamen ein. Anwendungen in gleicher Gruppe werden gruppiert angezeigt.</target>
@ -1419,9 +1415,6 @@
<trans-unit id="s350a616ff5e145ec">
<source>Select a provider that this application should use.</source>
</trans-unit>
<trans-unit id="scd5dff009d1fd847">
<source>Backchannel providers</source>
</trans-unit>
<trans-unit id="s4c6534a118f52fdd">
<source>Select backchannel providers which augment the functionality of the main provider.</source>
</trans-unit>
@ -1696,9 +1689,6 @@
<trans-unit id="sea3bfc143ced73db">
<source>NameID attribute</source>
</trans-unit>
<trans-unit id="sf05c693a6e270b79">
<source>SCIM provider is in preview.</source>
</trans-unit>
<trans-unit id="s2f0f6691de0b0388">
<source>Warning: Provider is not assigned to an application as backchannel provider.</source>
</trans-unit>
@ -1712,32 +1702,9 @@
<source>Run sync again</source>
<target>Synchronisation erneut ausführen</target>
</trans-unit>
<trans-unit id="sf6a518045ea398f7">
<source>Application details</source>
</trans-unit>
<trans-unit id="s51b0102a7d31d0bf">
<source>Create application</source>
</trans-unit>
<trans-unit id="sdae3a34cfb38c10f">
<source>Additional UI settings</source>
<target>Weitere UI-Einstellungen</target>
</trans-unit>
<trans-unit id="s233d106d13420dd2">
<source>OAuth2/OIDC</source>
</trans-unit>
<trans-unit id="sc2cedfb22488ccb2">
<source>Modern applications, APIs and Single-page applications.</source>
</trans-unit>
<trans-unit id="s1d93562422eeac10">
<source>SAML</source>
<target>SAML</target>
</trans-unit>
<trans-unit id="s5923f8a2226a414c">
<source>XML-based SSO standard. Use this if your application only supports SAML.</source>
</trans-unit>
<trans-unit id="s22f7a7ec3bc2c118">
<source>Legacy applications which don't natively support SSO.</source>
</trans-unit>
<trans-unit id="sc3259eb55cf91e8c">
<source>LDAP</source>
<target>LDAP</target>
@ -1745,108 +1712,9 @@
<trans-unit id="sffd5481034a1bd41">
<source>Provide an LDAP interface for applications and users to authenticate against.</source>
</trans-unit>
<trans-unit id="s3cf89cb47fdde7e9">
<source>Link</source>
<target>Link</target>
</trans-unit>
<trans-unit id="s7ccc013f41f0a2c2">
<source>Authentication method</source>
</trans-unit>
<trans-unit id="sd45fc6453f617152">
<source>LDAP details</source>
<target>LDAP-Details</target>
</trans-unit>
<trans-unit id="scf72690719b7a8a1">
<source>Create service account</source>
</trans-unit>
<trans-unit id="sbda06adbba493976">
<source>Create provider</source>
<target>Anbieter erstellen</target>
</trans-unit>
<trans-unit id="s12d0cb01d9f6f3b3">
<source>Application Link</source>
</trans-unit>
<trans-unit id="s54a13196915fd76c">
<source>URL which will be opened when a user clicks on the application.</source>
</trans-unit>
<trans-unit id="sa34866db5e891cc6">
<source>Method details</source>
</trans-unit>
<trans-unit id="saf840955862a8cd0">
<source>This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically.</source>
</trans-unit>
<trans-unit id="sb2f3a45baaa52dfd">
<source>By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password.</source>
</trans-unit>
<trans-unit id="se0df976add086ec3">
<source>Web application</source>
</trans-unit>
<trans-unit id="s042fb9b0f9acc21f">
<source>Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP)</source>
</trans-unit>
<trans-unit id="sdf4bd51375026222">
<source>Single-page applications</source>
</trans-unit>
<trans-unit id="s7fa3c3306b77187d">
<source>Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue)</source>
</trans-unit>
<trans-unit id="s3db92162f63a12b4">
<source>Native application</source>
</trans-unit>
<trans-unit id="s2f43274a59b0d384">
<source>Applications which redirect users to a non-web callback (for example, Android, iOS)</source>
</trans-unit>
<trans-unit id="sfa5f0019a09be6a7">
<source>API</source>
<target>API</target>
</trans-unit>
<trans-unit id="s4d53cad956dfc4e4">
<source>Authentication without user interaction, or machine-to-machine authentication.</source>
</trans-unit>
<trans-unit id="s134083b4613bcf87">
<source>Application type</source>
</trans-unit>
<trans-unit id="s649e7d74f27635f8">
<source>Flow used when users access this application.</source>
</trans-unit>
<trans-unit id="s9c1422a7e3d5c619">
<source>Proxy details</source>
<target>Proxy-Details</target>
</trans-unit>
<trans-unit id="s7714fdf13df8b682">
<source>External domain</source>
</trans-unit>
<trans-unit id="sf5a280bdcc775c49">
<source>External domain you will be accessing the domain from.</source>
</trans-unit>
<trans-unit id="sb11e750b3de9bb72">
<source>Import SAML Metadata</source>
</trans-unit>
<trans-unit id="s0955bd1d1b1dc710">
<source>Import the metadata document of the applicaation you want to configure.</source>
</trans-unit>
<trans-unit id="s0be509fcae72af45">
<source>Manual configuration</source>
</trans-unit>
<trans-unit id="s485b90ede24fdb19">
<source>Manually configure SAML</source>
</trans-unit>
<trans-unit id="se7d73f906d358a46">
<source>SAML details</source>
<target>SAML-Details</target>
</trans-unit>
<trans-unit id="s982a7cdc93aa6fbe">
<source>URL that authentik will redirect back to after successful authentication.</source>
</trans-unit>
<trans-unit id="sc9e75f44b775aa52">
<source>Import SAML metadata</source>
</trans-unit>
<trans-unit id="s0c9670f429e74283">
<source>New application</source>
</trans-unit>
<trans-unit id="s8e1d308c09d1b400">
<source>Create a new application.</source>
</trans-unit>
<trans-unit id="s6ba50bb0842ba1e2">
<source>Applications</source>
<target>Anwendungen</target>
@ -5933,6 +5801,76 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s24bce955914b1f0a">
<source>Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello).</source>
</trans-unit>
&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD
<trans-unit id="s1cffe58249b04669">
<source>Internal application name used in URLs.</source>
</trans-unit>
<trans-unit id="sb3d4f79d9d8b71e5">
<source>Submit</source>
</trans-unit>
<trans-unit id="se2b29e6cfe59414c">
<source>UI Settings</source>
</trans-unit>
<trans-unit id="sf45734291c57c606">
<source>OAuth2/OpenID</source>
</trans-unit>
<trans-unit id="s836148f721d8913b">
<source>Transparent Reverse Proxy</source>
</trans-unit>
<trans-unit id="s945a6b94361ee45b">
<source>For transparent reverse proxies with required authentication</source>
</trans-unit>
<trans-unit id="s60e79e1793e6bd56">
<source>Forward Auth Single Application</source>
</trans-unit>
<trans-unit id="sadf073913458acbd">
<source>For nginx's auth_request or traefix's forwardAuth</source>
</trans-unit>
<trans-unit id="s4425a1002597151e">
<source>Forward Auth Domain Level</source>
</trans-unit>
<trans-unit id="se770e9498b3bacf6">
<source>For nginx's auth_request or traefix's forwardAuth per root domain</source>
</trans-unit>
<trans-unit id="s40830ec037f34626">
<source>Configure SAML provider manually</source>
</trans-unit>
<trans-unit id="s154eea2a61d44655">
<source>RADIUS Configuration</source>
</trans-unit>
<trans-unit id="sea9fc40dfd1d18b1">
<source>Configure RADIUS provider manually</source>
</trans-unit>
<trans-unit id="s5a8f04c6ef292743">
<source>SCIM configuration</source>
</trans-unit>
<trans-unit id="sa1b0052ae095b9b3">
<source>Configure SCIM provider manually</source>
</trans-unit>
<trans-unit id="s15831fa50a116545">
<source>Saving Application...</source>
</trans-unit>
<trans-unit id="s823abdb61543a826">
<source>Authentik was unable to save this application:</source>
</trans-unit>
<trans-unit id="s848288f8c2265aad">
<source>Your application has been saved</source>
</trans-unit>
<trans-unit id="sf60f1e5b76897c93">
<source>In the Application:</source>
</trans-unit>
<trans-unit id="s7ce65cf482b7bff0">
<source>In the Provider:</source>
</trans-unit>
<trans-unit id="s67d858051b34c38b">
<source>Method's display Name.</source>
</trans-unit>
<trans-unit id="h10ef80d434185070">
<source>Use this provider with nginx's <x id="0" equiv-text="&lt;code&gt;"/>auth_request<x id="1" equiv-text="&lt;/code&gt;"/> or traefik's
<x id="2" equiv-text="&lt;code&gt;"/>forwardAuth<x id="3" equiv-text="&lt;/code&gt;"/>. Each application/domain needs its own provider.
Additionally, on each domain, <x id="4" equiv-text="&lt;code&gt;"/>/outpost.goauthentik.io<x id="5" equiv-text="&lt;/code&gt;"/> must be
routed to the outpost (when using a managed outpost, this is done for you).</source>
</trans-unit>
<trans-unit id="sd18b18f91b804c3f">
<source>Custom attributes</source>
</trans-unit>
@ -6043,6 +5981,9 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s526e2c66bd51ff5f">
<source>Role Info</source>
</trans-unit>
<trans-unit id="s2da4aa7a9abeb653">
<source>Pseudolocale (for testing)</source>
</trans-unit>
</body>
</file>

View file

@ -1475,10 +1475,6 @@
<source>Slug</source>
<target>Slug</target>
</trans-unit>
<trans-unit id="seb8407fa6607c683">
<source>Internal application name, used in URLs.</source>
<target>Internal application name, used in URLs.</target>
</trans-unit>
<trans-unit id="sdae55084f6cb2662">
<source>Optionally enter a group name. Applications with identical groups are shown grouped together.</source>
<target>Optionally enter a group name. Applications with identical groups are shown grouped together.</target>
@ -1491,10 +1487,6 @@
<source>Select a provider that this application should use.</source>
<target>Select a provider that this application should use.</target>
</trans-unit>
<trans-unit id="scd5dff009d1fd847">
<source>Backchannel providers</source>
<target>Backchannel providers</target>
</trans-unit>
<trans-unit id="s4c6534a118f52fdd">
<source>Select backchannel providers which augment the functionality of the main provider.</source>
<target>Select backchannel providers which augment the functionality of the main provider.</target>
@ -1787,10 +1779,6 @@
<source>NameID attribute</source>
<target>NameID attribute</target>
</trans-unit>
<trans-unit id="sf05c693a6e270b79">
<source>SCIM provider is in preview.</source>
<target>SCIM provider is in preview.</target>
</trans-unit>
<trans-unit id="s2f0f6691de0b0388">
<source>Warning: Provider is not assigned to an application as backchannel provider.</source>
<target>Warning: Provider is not assigned to an application as backchannel provider.</target>
@ -1807,38 +1795,10 @@
<source>Run sync again</source>
<target>Run sync again</target>
</trans-unit>
<trans-unit id="sf6a518045ea398f7">
<source>Application details</source>
<target>Application details</target>
</trans-unit>
<trans-unit id="s51b0102a7d31d0bf">
<source>Create application</source>
<target>Create application</target>
</trans-unit>
<trans-unit id="sdae3a34cfb38c10f">
<source>Additional UI settings</source>
<target>Additional UI settings</target>
</trans-unit>
<trans-unit id="s233d106d13420dd2">
<source>OAuth2/OIDC</source>
<target>OAuth2/OIDC</target>
</trans-unit>
<trans-unit id="sc2cedfb22488ccb2">
<source>Modern applications, APIs and Single-page applications.</source>
<target>Modern applications, APIs and Single-page applications.</target>
</trans-unit>
<trans-unit id="s1d93562422eeac10">
<source>SAML</source>
<target>SAML</target>
</trans-unit>
<trans-unit id="s5923f8a2226a414c">
<source>XML-based SSO standard. Use this if your application only supports SAML.</source>
<target>XML-based SSO standard. Use this if your application only supports SAML.</target>
</trans-unit>
<trans-unit id="s22f7a7ec3bc2c118">
<source>Legacy applications which don't natively support SSO.</source>
<target>Legacy applications which don't natively support SSO.</target>
</trans-unit>
<trans-unit id="sc3259eb55cf91e8c">
<source>LDAP</source>
<target>LDAP</target>
@ -1847,134 +1807,10 @@
<source>Provide an LDAP interface for applications and users to authenticate against.</source>
<target>Provide an LDAP interface for applications and users to authenticate against.</target>
</trans-unit>
<trans-unit id="s3cf89cb47fdde7e9">
<source>Link</source>
<target>Link</target>
</trans-unit>
<trans-unit id="s7ccc013f41f0a2c2">
<source>Authentication method</source>
<target>Authentication method</target>
</trans-unit>
<trans-unit id="sd45fc6453f617152">
<source>LDAP details</source>
<target>LDAP details</target>
</trans-unit>
<trans-unit id="scf72690719b7a8a1">
<source>Create service account</source>
<target>Create service account</target>
</trans-unit>
<trans-unit id="sbda06adbba493976">
<source>Create provider</source>
<target>Create provider</target>
</trans-unit>
<trans-unit id="s12d0cb01d9f6f3b3">
<source>Application Link</source>
<target>Application Link</target>
</trans-unit>
<trans-unit id="s54a13196915fd76c">
<source>URL which will be opened when a user clicks on the application.</source>
<target>URL which will be opened when a user clicks on the application.</target>
</trans-unit>
<trans-unit id="sa34866db5e891cc6">
<source>Method details</source>
<target>Method details</target>
</trans-unit>
<trans-unit id="saf840955862a8cd0">
<source>This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically.</source>
<target>This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically.</target>
</trans-unit>
<trans-unit id="sb2f3a45baaa52dfd">
<source>By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password.</source>
<target>By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password.</target>
</trans-unit>
<trans-unit id="se0df976add086ec3">
<source>Web application</source>
<target>Web application</target>
</trans-unit>
<trans-unit id="s042fb9b0f9acc21f">
<source>Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP)</source>
<target>Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP)</target>
</trans-unit>
<trans-unit id="sdf4bd51375026222">
<source>Single-page applications</source>
<target>Single-page applications</target>
</trans-unit>
<trans-unit id="s7fa3c3306b77187d">
<source>Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue)</source>
<target>Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue)</target>
</trans-unit>
<trans-unit id="s3db92162f63a12b4">
<source>Native application</source>
<target>Native application</target>
</trans-unit>
<trans-unit id="s2f43274a59b0d384">
<source>Applications which redirect users to a non-web callback (for example, Android, iOS)</source>
<target>Applications which redirect users to a non-web callback (for example, Android, iOS)</target>
</trans-unit>
<trans-unit id="sfa5f0019a09be6a7">
<source>API</source>
<target>API</target>
</trans-unit>
<trans-unit id="s4d53cad956dfc4e4">
<source>Authentication without user interaction, or machine-to-machine authentication.</source>
<target>Authentication without user interaction, or machine-to-machine authentication.</target>
</trans-unit>
<trans-unit id="s134083b4613bcf87">
<source>Application type</source>
<target>Application type</target>
</trans-unit>
<trans-unit id="s649e7d74f27635f8">
<source>Flow used when users access this application.</source>
<target>Flow used when users access this application.</target>
</trans-unit>
<trans-unit id="s9c1422a7e3d5c619">
<source>Proxy details</source>
<target>Proxy details</target>
</trans-unit>
<trans-unit id="s7714fdf13df8b682">
<source>External domain</source>
<target>External domain</target>
</trans-unit>
<trans-unit id="sf5a280bdcc775c49">
<source>External domain you will be accessing the domain from.</source>
<target>External domain you will be accessing the domain from.</target>
</trans-unit>
<trans-unit id="sb11e750b3de9bb72">
<source>Import SAML Metadata</source>
<target>Import SAML Metadata</target>
</trans-unit>
<trans-unit id="s0955bd1d1b1dc710">
<source>Import the metadata document of the applicaation you want to configure.</source>
<target>Import the metadata document of the applicaation you want to configure.</target>
</trans-unit>
<trans-unit id="s0be509fcae72af45">
<source>Manual configuration</source>
<target>Manual configuration</target>
</trans-unit>
<trans-unit id="s485b90ede24fdb19">
<source>Manually configure SAML</source>
<target>Manually configure SAML</target>
</trans-unit>
<trans-unit id="se7d73f906d358a46">
<source>SAML details</source>
<target>SAML details</target>
</trans-unit>
<trans-unit id="s982a7cdc93aa6fbe">
<source>URL that authentik will redirect back to after successful authentication.</source>
<target>URL that authentik will redirect back to after successful authentication.</target>
</trans-unit>
<trans-unit id="sc9e75f44b775aa52">
<source>Import SAML metadata</source>
<target>Import SAML metadata</target>
</trans-unit>
<trans-unit id="s0c9670f429e74283">
<source>New application</source>
<target>New application</target>
</trans-unit>
<trans-unit id="s8e1d308c09d1b400">
<source>Create a new application.</source>
<target>Create a new application.</target>
</trans-unit>
<trans-unit id="s6ba50bb0842ba1e2">
<source>Applications</source>
<target>Applications</target>
@ -6247,6 +6083,76 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s24bce955914b1f0a">
<source>Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello).</source>
</trans-unit>
&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD
<trans-unit id="s1cffe58249b04669">
<source>Internal application name used in URLs.</source>
</trans-unit>
<trans-unit id="sb3d4f79d9d8b71e5">
<source>Submit</source>
</trans-unit>
<trans-unit id="se2b29e6cfe59414c">
<source>UI Settings</source>
</trans-unit>
<trans-unit id="sf45734291c57c606">
<source>OAuth2/OpenID</source>
</trans-unit>
<trans-unit id="s836148f721d8913b">
<source>Transparent Reverse Proxy</source>
</trans-unit>
<trans-unit id="s945a6b94361ee45b">
<source>For transparent reverse proxies with required authentication</source>
</trans-unit>
<trans-unit id="s60e79e1793e6bd56">
<source>Forward Auth Single Application</source>
</trans-unit>
<trans-unit id="sadf073913458acbd">
<source>For nginx's auth_request or traefix's forwardAuth</source>
</trans-unit>
<trans-unit id="s4425a1002597151e">
<source>Forward Auth Domain Level</source>
</trans-unit>
<trans-unit id="se770e9498b3bacf6">
<source>For nginx's auth_request or traefix's forwardAuth per root domain</source>
</trans-unit>
<trans-unit id="s40830ec037f34626">
<source>Configure SAML provider manually</source>
</trans-unit>
<trans-unit id="s154eea2a61d44655">
<source>RADIUS Configuration</source>
</trans-unit>
<trans-unit id="sea9fc40dfd1d18b1">
<source>Configure RADIUS provider manually</source>
</trans-unit>
<trans-unit id="s5a8f04c6ef292743">
<source>SCIM configuration</source>
</trans-unit>
<trans-unit id="sa1b0052ae095b9b3">
<source>Configure SCIM provider manually</source>
</trans-unit>
<trans-unit id="s15831fa50a116545">
<source>Saving Application...</source>
</trans-unit>
<trans-unit id="s823abdb61543a826">
<source>Authentik was unable to save this application:</source>
</trans-unit>
<trans-unit id="s848288f8c2265aad">
<source>Your application has been saved</source>
</trans-unit>
<trans-unit id="sf60f1e5b76897c93">
<source>In the Application:</source>
</trans-unit>
<trans-unit id="s7ce65cf482b7bff0">
<source>In the Provider:</source>
</trans-unit>
<trans-unit id="s67d858051b34c38b">
<source>Method's display Name.</source>
</trans-unit>
<trans-unit id="h10ef80d434185070">
<source>Use this provider with nginx's <x id="0" equiv-text="&lt;code&gt;"/>auth_request<x id="1" equiv-text="&lt;/code&gt;"/> or traefik's
<x id="2" equiv-text="&lt;code&gt;"/>forwardAuth<x id="3" equiv-text="&lt;/code&gt;"/>. Each application/domain needs its own provider.
Additionally, on each domain, <x id="4" equiv-text="&lt;code&gt;"/>/outpost.goauthentik.io<x id="5" equiv-text="&lt;/code&gt;"/> must be
routed to the outpost (when using a managed outpost, this is done for you).</source>
</trans-unit>
<trans-unit id="sd18b18f91b804c3f">
<source>Custom attributes</source>
</trans-unit>
@ -6357,6 +6263,9 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s526e2c66bd51ff5f">
<source>Role Info</source>
</trans-unit>
<trans-unit id="s2da4aa7a9abeb653">
<source>Pseudolocale (for testing)</source>
</trans-unit>
</body>
</file>

View file

@ -1378,10 +1378,6 @@
<source>Slug</source>
<target>babosa</target>
</trans-unit>
<trans-unit id="seb8407fa6607c683">
<source>Internal application name, used in URLs.</source>
<target>Nombre de la aplicación interna, utilizado en las URL.</target>
</trans-unit>
<trans-unit id="sdae55084f6cb2662">
<source>Optionally enter a group name. Applications with identical groups are shown grouped together.</source>
</trans-unit>
@ -1392,9 +1388,6 @@
<trans-unit id="s350a616ff5e145ec">
<source>Select a provider that this application should use.</source>
</trans-unit>
<trans-unit id="scd5dff009d1fd847">
<source>Backchannel providers</source>
</trans-unit>
<trans-unit id="s4c6534a118f52fdd">
<source>Select backchannel providers which augment the functionality of the main provider.</source>
</trans-unit>
@ -1668,9 +1661,6 @@
<trans-unit id="sea3bfc143ced73db">
<source>NameID attribute</source>
</trans-unit>
<trans-unit id="sf05c693a6e270b79">
<source>SCIM provider is in preview.</source>
</trans-unit>
<trans-unit id="s2f0f6691de0b0388">
<source>Warning: Provider is not assigned to an application as backchannel provider.</source>
</trans-unit>
@ -1684,30 +1674,9 @@
<source>Run sync again</source>
<target>Vuelve a ejecutar la sincronización</target>
</trans-unit>
<trans-unit id="sf6a518045ea398f7">
<source>Application details</source>
</trans-unit>
<trans-unit id="s51b0102a7d31d0bf">
<source>Create application</source>
</trans-unit>
<trans-unit id="sdae3a34cfb38c10f">
<source>Additional UI settings</source>
</trans-unit>
<trans-unit id="s233d106d13420dd2">
<source>OAuth2/OIDC</source>
</trans-unit>
<trans-unit id="sc2cedfb22488ccb2">
<source>Modern applications, APIs and Single-page applications.</source>
</trans-unit>
<trans-unit id="s1d93562422eeac10">
<source>SAML</source>
</trans-unit>
<trans-unit id="s5923f8a2226a414c">
<source>XML-based SSO standard. Use this if your application only supports SAML.</source>
</trans-unit>
<trans-unit id="s22f7a7ec3bc2c118">
<source>Legacy applications which don't natively support SSO.</source>
</trans-unit>
<trans-unit id="sc3259eb55cf91e8c">
<source>LDAP</source>
<target>LDAP</target>
@ -1715,103 +1684,9 @@
<trans-unit id="sffd5481034a1bd41">
<source>Provide an LDAP interface for applications and users to authenticate against.</source>
</trans-unit>
<trans-unit id="s3cf89cb47fdde7e9">
<source>Link</source>
</trans-unit>
<trans-unit id="s7ccc013f41f0a2c2">
<source>Authentication method</source>
</trans-unit>
<trans-unit id="sd45fc6453f617152">
<source>LDAP details</source>
</trans-unit>
<trans-unit id="scf72690719b7a8a1">
<source>Create service account</source>
</trans-unit>
<trans-unit id="sbda06adbba493976">
<source>Create provider</source>
<target>Crear proveedor</target>
</trans-unit>
<trans-unit id="s12d0cb01d9f6f3b3">
<source>Application Link</source>
</trans-unit>
<trans-unit id="s54a13196915fd76c">
<source>URL which will be opened when a user clicks on the application.</source>
</trans-unit>
<trans-unit id="sa34866db5e891cc6">
<source>Method details</source>
</trans-unit>
<trans-unit id="saf840955862a8cd0">
<source>This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically.</source>
</trans-unit>
<trans-unit id="sb2f3a45baaa52dfd">
<source>By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password.</source>
</trans-unit>
<trans-unit id="se0df976add086ec3">
<source>Web application</source>
</trans-unit>
<trans-unit id="s042fb9b0f9acc21f">
<source>Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP)</source>
</trans-unit>
<trans-unit id="sdf4bd51375026222">
<source>Single-page applications</source>
</trans-unit>
<trans-unit id="s7fa3c3306b77187d">
<source>Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue)</source>
</trans-unit>
<trans-unit id="s3db92162f63a12b4">
<source>Native application</source>
</trans-unit>
<trans-unit id="s2f43274a59b0d384">
<source>Applications which redirect users to a non-web callback (for example, Android, iOS)</source>
</trans-unit>
<trans-unit id="sfa5f0019a09be6a7">
<source>API</source>
</trans-unit>
<trans-unit id="s4d53cad956dfc4e4">
<source>Authentication without user interaction, or machine-to-machine authentication.</source>
</trans-unit>
<trans-unit id="s134083b4613bcf87">
<source>Application type</source>
</trans-unit>
<trans-unit id="s649e7d74f27635f8">
<source>Flow used when users access this application.</source>
</trans-unit>
<trans-unit id="s9c1422a7e3d5c619">
<source>Proxy details</source>
</trans-unit>
<trans-unit id="s7714fdf13df8b682">
<source>External domain</source>
</trans-unit>
<trans-unit id="sf5a280bdcc775c49">
<source>External domain you will be accessing the domain from.</source>
</trans-unit>
<trans-unit id="sb11e750b3de9bb72">
<source>Import SAML Metadata</source>
</trans-unit>
<trans-unit id="s0955bd1d1b1dc710">
<source>Import the metadata document of the applicaation you want to configure.</source>
</trans-unit>
<trans-unit id="s0be509fcae72af45">
<source>Manual configuration</source>
</trans-unit>
<trans-unit id="s485b90ede24fdb19">
<source>Manually configure SAML</source>
</trans-unit>
<trans-unit id="se7d73f906d358a46">
<source>SAML details</source>
</trans-unit>
<trans-unit id="s982a7cdc93aa6fbe">
<source>URL that authentik will redirect back to after successful authentication.</source>
</trans-unit>
<trans-unit id="sc9e75f44b775aa52">
<source>Import SAML metadata</source>
</trans-unit>
<trans-unit id="s0c9670f429e74283">
<source>New application</source>
</trans-unit>
<trans-unit id="s8e1d308c09d1b400">
<source>Create a new application.</source>
</trans-unit>
<trans-unit id="s6ba50bb0842ba1e2">
<source>Applications</source>
<target>Aplicaciones</target>
@ -5841,6 +5716,76 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s24bce955914b1f0a">
<source>Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello).</source>
</trans-unit>
&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD
<trans-unit id="s1cffe58249b04669">
<source>Internal application name used in URLs.</source>
</trans-unit>
<trans-unit id="sb3d4f79d9d8b71e5">
<source>Submit</source>
</trans-unit>
<trans-unit id="se2b29e6cfe59414c">
<source>UI Settings</source>
</trans-unit>
<trans-unit id="sf45734291c57c606">
<source>OAuth2/OpenID</source>
</trans-unit>
<trans-unit id="s836148f721d8913b">
<source>Transparent Reverse Proxy</source>
</trans-unit>
<trans-unit id="s945a6b94361ee45b">
<source>For transparent reverse proxies with required authentication</source>
</trans-unit>
<trans-unit id="s60e79e1793e6bd56">
<source>Forward Auth Single Application</source>
</trans-unit>
<trans-unit id="sadf073913458acbd">
<source>For nginx's auth_request or traefix's forwardAuth</source>
</trans-unit>
<trans-unit id="s4425a1002597151e">
<source>Forward Auth Domain Level</source>
</trans-unit>
<trans-unit id="se770e9498b3bacf6">
<source>For nginx's auth_request or traefix's forwardAuth per root domain</source>
</trans-unit>
<trans-unit id="s40830ec037f34626">
<source>Configure SAML provider manually</source>
</trans-unit>
<trans-unit id="s154eea2a61d44655">
<source>RADIUS Configuration</source>
</trans-unit>
<trans-unit id="sea9fc40dfd1d18b1">
<source>Configure RADIUS provider manually</source>
</trans-unit>
<trans-unit id="s5a8f04c6ef292743">
<source>SCIM configuration</source>
</trans-unit>
<trans-unit id="sa1b0052ae095b9b3">
<source>Configure SCIM provider manually</source>
</trans-unit>
<trans-unit id="s15831fa50a116545">
<source>Saving Application...</source>
</trans-unit>
<trans-unit id="s823abdb61543a826">
<source>Authentik was unable to save this application:</source>
</trans-unit>
<trans-unit id="s848288f8c2265aad">
<source>Your application has been saved</source>
</trans-unit>
<trans-unit id="sf60f1e5b76897c93">
<source>In the Application:</source>
</trans-unit>
<trans-unit id="s7ce65cf482b7bff0">
<source>In the Provider:</source>
</trans-unit>
<trans-unit id="s67d858051b34c38b">
<source>Method's display Name.</source>
</trans-unit>
<trans-unit id="h10ef80d434185070">
<source>Use this provider with nginx's <x id="0" equiv-text="&lt;code&gt;"/>auth_request<x id="1" equiv-text="&lt;/code&gt;"/> or traefik's
<x id="2" equiv-text="&lt;code&gt;"/>forwardAuth<x id="3" equiv-text="&lt;/code&gt;"/>. Each application/domain needs its own provider.
Additionally, on each domain, <x id="4" equiv-text="&lt;code&gt;"/>/outpost.goauthentik.io<x id="5" equiv-text="&lt;/code&gt;"/> must be
routed to the outpost (when using a managed outpost, this is done for you).</source>
</trans-unit>
<trans-unit id="sd18b18f91b804c3f">
<source>Custom attributes</source>
</trans-unit>
@ -5951,6 +5896,9 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s526e2c66bd51ff5f">
<source>Role Info</source>
</trans-unit>
<trans-unit id="s2da4aa7a9abeb653">
<source>Pseudolocale (for testing)</source>
</trans-unit>
</body>
</file>

View file

@ -613,9 +613,9 @@ Il y a <x id="0" equiv-text="${ago}"/> jour(s)</target>
</trans-unit>
<trans-unit id="saa0e2675da69651b">
<source>The URL &quot;<x id="0" equiv-text="${this.url}"/>&quot; was not found.</source>
<target>L'URL &quot;
<x id="0" equiv-text="${this.url}"/>&quot; n'a pas été trouvée.</target>
<source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source>
<target>L'URL "
<x id="0" equiv-text="${this.url}"/>" n'a pas été trouvée.</target>
</trans-unit>
<trans-unit id="s58cd9c2fe836d9c6">
@ -1067,8 +1067,8 @@ Il y a <x id="0" equiv-text="${ago}"/> jour(s)</target>
</trans-unit>
<trans-unit id="sa8384c9c26731f83">
<source>To allow any redirect URI, set this value to &quot;.*&quot;. Be aware of the possible security implications this can have.</source>
<target>Pour permettre n'importe quelle URI de redirection, définissez cette valeur sur &quot;.*&quot;. Soyez conscient des possibles implications de sécurité que cela peut avoir.</target>
<source>To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have.</source>
<target>Pour permettre n'importe quelle URI de redirection, définissez cette valeur sur ".*". Soyez conscient des possibles implications de sécurité que cela peut avoir.</target>
</trans-unit>
<trans-unit id="s55787f4dfcdce52b">
@ -1640,7 +1640,7 @@ Il y a <x id="0" equiv-text="${ago}"/> jour(s)</target>
</trans-unit>
<trans-unit id="s33ed903c210a6209">
<source>Token to authenticate with. Currently only bearer authentication is supported.</source>
<target>Jeton d'authentification à utiliser. Actuellement, seule l'authentification &quot;bearer authentication&quot; est prise en charge.</target>
<target>Jeton d'authentification à utiliser. Actuellement, seule l'authentification "bearer authentication" est prise en charge.</target>
</trans-unit>
<trans-unit id="sfc8bb104e2c05af8">
@ -1808,8 +1808,8 @@ Il y a <x id="0" equiv-text="${ago}"/> jour(s)</target>
</trans-unit>
<trans-unit id="sa90b7809586c35ce">
<source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon &quot;fa-test&quot;.</source>
<target>Entrez une URL complète, un chemin relatif ou utilisez 'fa://fa-test' pour utiliser l'icône Font Awesome &quot;fa-test&quot;.</target>
<source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".</source>
<target>Entrez une URL complète, un chemin relatif ou utilisez 'fa://fa-test' pour utiliser l'icône Font Awesome "fa-test".</target>
</trans-unit>
<trans-unit id="s0410779cb47de312">
@ -1836,11 +1836,6 @@ Il y a <x id="0" equiv-text="${ago}"/> jour(s)</target>
<source>Slug</source>
<target>Slug</target>
</trans-unit>
<trans-unit id="seb8407fa6607c683">
<source>Internal application name, used in URLs.</source>
<target>Nom de l'application interne, utilisé dans les URLs.</target>
</trans-unit>
<trans-unit id="sdae55084f6cb2662">
<source>Optionally enter a group name. Applications with identical groups are shown grouped together.</source>
@ -1856,11 +1851,6 @@ Il y a <x id="0" equiv-text="${ago}"/> jour(s)</target>
<source>Select a provider that this application should use.</source>
<target>Sélectionnez un fournisseur que cette application doit utiliser.</target>
</trans-unit>
<trans-unit id="scd5dff009d1fd847">
<source>Backchannel providers</source>
<target>Fournisseurs backchannel</target>
</trans-unit>
<trans-unit id="s4c6534a118f52fdd">
<source>Select backchannel providers which augment the functionality of the main provider.</source>
@ -2226,11 +2216,6 @@ Il y a <x id="0" equiv-text="${ago}"/> jour(s)</target>
<source>NameID attribute</source>
<target>Attribut NameID</target>
</trans-unit>
<trans-unit id="sf05c693a6e270b79">
<source>SCIM provider is in preview.</source>
<target>Le fournisseur SCIM est en aperçu.</target>
</trans-unit>
<trans-unit id="s2f0f6691de0b0388">
<source>Warning: Provider is not assigned to an application as backchannel provider.</source>
@ -2251,46 +2236,11 @@ Il y a <x id="0" equiv-text="${ago}"/> jour(s)</target>
<source>Run sync again</source>
<target>Relancer la synchro</target>
</trans-unit>
<trans-unit id="sf6a518045ea398f7">
<source>Application details</source>
<target>Détails de l'application</target>
</trans-unit>
<trans-unit id="s51b0102a7d31d0bf">
<source>Create application</source>
<target>Créer une application</target>
</trans-unit>
<trans-unit id="sdae3a34cfb38c10f">
<source>Additional UI settings</source>
<target>Paramètres d'interface additionnels</target>
</trans-unit>
<trans-unit id="s233d106d13420dd2">
<source>OAuth2/OIDC</source>
<target>OAuth2/OIDC</target>
</trans-unit>
<trans-unit id="sc2cedfb22488ccb2">
<source>Modern applications, APIs and Single-page applications.</source>
<target>Applications modernes, API et applications à page unique.</target>
</trans-unit>
<trans-unit id="s1d93562422eeac10">
<source>SAML</source>
<target>SAML</target>
</trans-unit>
<trans-unit id="s5923f8a2226a414c">
<source>XML-based SSO standard. Use this if your application only supports SAML.</source>
<target>Norme SSO basée sur XML. Utilisez cette option si votre application ne soutient que SAML.</target>
</trans-unit>
<trans-unit id="s22f7a7ec3bc2c118">
<source>Legacy applications which don't natively support SSO.</source>
<target>Les applications anciennes qui ne supportent pas nativement un SSO.</target>
</trans-unit>
<trans-unit id="sc3259eb55cf91e8c">
<source>LDAP</source>
@ -2301,166 +2251,11 @@ Il y a <x id="0" equiv-text="${ago}"/> jour(s)</target>
<source>Provide an LDAP interface for applications and users to authenticate against.</source>
<target>Fournir une interface LDAP permettant aux applications et aux utilisateurs de s'authentifier.</target>
</trans-unit>
<trans-unit id="s3cf89cb47fdde7e9">
<source>Link</source>
<target>Lien</target>
</trans-unit>
<trans-unit id="s7ccc013f41f0a2c2">
<source>Authentication method</source>
<target>Méthode d'authentification</target>
</trans-unit>
<trans-unit id="sd45fc6453f617152">
<source>LDAP details</source>
<target>Détails LDAP</target>
</trans-unit>
<trans-unit id="scf72690719b7a8a1">
<source>Create service account</source>
<target>Créer un compte de service</target>
</trans-unit>
<trans-unit id="sbda06adbba493976">
<source>Create provider</source>
<target>Créer un fournisseur</target>
</trans-unit>
<trans-unit id="s12d0cb01d9f6f3b3">
<source>Application Link</source>
<target>Lien de lapplication</target>
</trans-unit>
<trans-unit id="s54a13196915fd76c">
<source>URL which will be opened when a user clicks on the application.</source>
<target>URL qui sera ouverte lorsqu'un utilisateur clique sur l'application.</target>
</trans-unit>
<trans-unit id="sa34866db5e891cc6">
<source>Method details</source>
<target>Détails de la méthode</target>
</trans-unit>
<trans-unit id="saf840955862a8cd0">
<source>This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically.</source>
<target>Cette configuration peut être utilisée pour s'authentifier auprès d'authentik avec d'autres API ou de manière programmatique.</target>
</trans-unit>
<trans-unit id="sb2f3a45baaa52dfd">
<source>By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password.</source>
<target>Par défaut, tous les comptes de services peuvent se connecter à cette application, tant qu'ils ont un jeton valide du type app-password.</target>
</trans-unit>
<trans-unit id="se0df976add086ec3">
<source>Web application</source>
<target>Application Web</target>
</trans-unit>
<trans-unit id="s042fb9b0f9acc21f">
<source>Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP)</source>
<target>Applications qui soccupent de lauthentification côté serveur (par exemple Python, Go, Rust, Java, PHP)</target>
</trans-unit>
<trans-unit id="sdf4bd51375026222">
<source>Single-page applications</source>
<target>Applications à page unique</target>
</trans-unit>
<trans-unit id="s7fa3c3306b77187d">
<source>Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue)</source>
<target>Applications à page unique qui gèrent l'authentification dans le navigateur (par exemple, Javascript, Angular, React, Vue).</target>
</trans-unit>
<trans-unit id="s3db92162f63a12b4">
<source>Native application</source>
<target>Application native</target>
</trans-unit>
<trans-unit id="s2f43274a59b0d384">
<source>Applications which redirect users to a non-web callback (for example, Android, iOS)</source>
<target>Applications qui redirigent les utilisateurs vers un callback non web (par exemple, Android, iOS)</target>
</trans-unit>
<trans-unit id="sfa5f0019a09be6a7">
<source>API</source>
<target>API</target>
</trans-unit>
<trans-unit id="s4d53cad956dfc4e4">
<source>Authentication without user interaction, or machine-to-machine authentication.</source>
<target>Authentification sans interaction avec l'utilisateur, ou authentification de machine à machine.</target>
</trans-unit>
<trans-unit id="s134083b4613bcf87">
<source>Application type</source>
<target>Type dapplication</target>
</trans-unit>
<trans-unit id="s649e7d74f27635f8">
<source>Flow used when users access this application.</source>
<target>Flux utilisé lorsque les utilisateurs accèdent à cette application.</target>
</trans-unit>
<trans-unit id="s9c1422a7e3d5c619">
<source>Proxy details</source>
<target>Détails du Proxy</target>
</trans-unit>
<trans-unit id="s7714fdf13df8b682">
<source>External domain</source>
<target>Domaine externe</target>
</trans-unit>
<trans-unit id="sf5a280bdcc775c49">
<source>External domain you will be accessing the domain from.</source>
<target>Domaine externe à partir duquel vous accéderez au domaine.</target>
</trans-unit>
<trans-unit id="sb11e750b3de9bb72">
<source>Import SAML Metadata</source>
<target>Importer des métadonnées SAML</target>
</trans-unit>
<trans-unit id="s0955bd1d1b1dc710">
<source>Import the metadata document of the applicaation you want to configure.</source>
<target>Importez le document de métadonnées de l'application que vous souhaitez configurer.</target>
</trans-unit>
<trans-unit id="s0be509fcae72af45">
<source>Manual configuration</source>
<target>Configuration manuelle</target>
</trans-unit>
<trans-unit id="s485b90ede24fdb19">
<source>Manually configure SAML</source>
<target>Configurer SAML manuellement</target>
</trans-unit>
<trans-unit id="se7d73f906d358a46">
<source>SAML details</source>
<target>Détails SAML</target>
</trans-unit>
<trans-unit id="s982a7cdc93aa6fbe">
<source>URL that authentik will redirect back to after successful authentication.</source>
<target>URL vers laquelle authentik redirigera après une authentification réussie.</target>
</trans-unit>
<trans-unit id="sc9e75f44b775aa52">
<source>Import SAML metadata</source>
<target>Importer des métadonnées SAML</target>
</trans-unit>
<trans-unit id="s0c9670f429e74283">
<source>New application</source>
<target>Nouvelle application</target>
</trans-unit>
<trans-unit id="s8e1d308c09d1b400">
<source>Create a new application.</source>
<target>Créer une nouvelle application.</target>
</trans-unit>
<trans-unit id="s6ba50bb0842ba1e2">
<source>Applications</source>
@ -3142,7 +2937,7 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit>
<trans-unit id="s33683c3b1dbaf264">
<source>To use SSL instead, use 'ldaps://' and disable this option.</source>
<target>Pour utiliser SSL à la base, utilisez &quot;ldaps://&quot; et désactviez cette option.</target>
<target>Pour utiliser SSL à la base, utilisez "ldaps://" et désactviez cette option.</target>
</trans-unit>
<trans-unit id="s2221fef80f4753a2">
@ -3231,8 +3026,8 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit>
<trans-unit id="s76768bebabb7d543">
<source>Field which contains members of a group. Note that if using the &quot;memberUid&quot; field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source>
<target>Champ qui contient les membres d'un groupe. Si vous utilisez le champ &quot;memberUid&quot;, la valeur est censée contenir un nom distinctif relatif, par exemple 'memberUid=un-utilisateur' au lieu de 'memberUid=cn=un-utilisateur,ou=groups,...'</target>
<source>Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source>
<target>Champ qui contient les membres d'un groupe. Si vous utilisez le champ "memberUid", la valeur est censée contenir un nom distinctif relatif, par exemple 'memberUid=un-utilisateur' au lieu de 'memberUid=cn=un-utilisateur,ou=groups,...'</target>
</trans-unit>
<trans-unit id="s026555347e589f0e">
@ -3527,7 +3322,7 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit>
<trans-unit id="s3198c384c2f68b08">
<source>Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually.</source>
<target>Moment où les utilisateurs temporaires doivent être supprimés. Cela ne s'applique que si votre IDP utilise le format NameID &quot;transient&quot; et que l'utilisateur ne se déconnecte pas manuellement.</target>
<target>Moment où les utilisateurs temporaires doivent être supprimés. Cela ne s'applique que si votre IDP utilise le format NameID "transient" et que l'utilisateur ne se déconnecte pas manuellement.</target>
</trans-unit>
<trans-unit id="sb32e9c1faa0b8673">
@ -3695,7 +3490,7 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit>
<trans-unit id="s9f8aac89fe318acc">
<source>Optionally set the 'FriendlyName' value of the Assertion attribute.</source>
<target>Indiquer la valeur &quot;FriendlyName&quot; de l'attribut d'assertion (optionnel)</target>
<target>Indiquer la valeur "FriendlyName" de l'attribut d'assertion (optionnel)</target>
</trans-unit>
<trans-unit id="s851c108679653d2a">
@ -4024,8 +3819,8 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit>
<trans-unit id="s7b1fba26d245cb1c">
<source>When using an external logging solution for archiving, this can be set to &quot;minutes=5&quot;.</source>
<target>En cas d'utilisation d'une solution de journalisation externe pour l'archivage, cette valeur peut être fixée à &quot;minutes=5&quot;.</target>
<source>When using an external logging solution for archiving, this can be set to "minutes=5".</source>
<target>En cas d'utilisation d'une solution de journalisation externe pour l'archivage, cette valeur peut être fixée à "minutes=5".</target>
</trans-unit>
<trans-unit id="s44536d20bb5c8257">
@ -4034,8 +3829,8 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit>
<trans-unit id="s3bb51cabb02b997e">
<source>Format: &quot;weeks=3;days=2;hours=3,seconds=2&quot;.</source>
<target>Format : &quot;weeks=3;days=2;hours=3,seconds=2&quot;.</target>
<source>Format: "weeks=3;days=2;hours=3,seconds=2".</source>
<target>Format : "weeks=3;days=2;hours=3,seconds=2".</target>
</trans-unit>
<trans-unit id="s04bfd02201db5ab8">
@ -4231,10 +4026,10 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit>
<trans-unit id="sa95a538bfbb86111">
<source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> &quot;<x id="1" equiv-text="${this.obj?.name}"/>&quot;?</source>
<source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</source>
<target>Êtes-vous sûr de vouloir mettre à jour
<x id="0" equiv-text="${this.objectLabel}"/>&quot;
<x id="1" equiv-text="${this.obj?.name}"/>&quot; ?</target>
<x id="0" equiv-text="${this.objectLabel}"/>"
<x id="1" equiv-text="${this.obj?.name}"/>" ?</target>
</trans-unit>
<trans-unit id="sc92d7cfb6ee1fec6">
@ -5330,8 +5125,8 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit>
<trans-unit id="sdf1d8edef27236f0">
<source>A &quot;roaming&quot; authenticator, like a YubiKey</source>
<target>Un authentificateur &quot;itinérant&quot;, comme une YubiKey</target>
<source>A "roaming" authenticator, like a YubiKey</source>
<target>Un authentificateur "itinérant", comme une YubiKey</target>
</trans-unit>
<trans-unit id="sfffba7b23d8fb40c">
@ -5656,7 +5451,7 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit>
<trans-unit id="s5170f9ef331949c0">
<source>Show arbitrary input fields to the user, for example during enrollment. Data is saved in the flow context under the 'prompt_data' variable.</source>
<target>Afficher des champs de saisie arbitraires à l'utilisateur, par exemple pendant l'inscription. Les données sont enregistrées dans le contexte du flux sous la variable &quot;prompt_data&quot;.</target>
<target>Afficher des champs de saisie arbitraires à l'utilisateur, par exemple pendant l'inscription. Les données sont enregistrées dans le contexte du flux sous la variable "prompt_data".</target>
</trans-unit>
<trans-unit id="s36cb242ac90353bc">
@ -5665,10 +5460,10 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit>
<trans-unit id="s2d5f69929bb7221d">
<source><x id="0" equiv-text="${prompt.name}"/> (&quot;<x id="1" equiv-text="${prompt.fieldKey}"/>&quot;, of type <x id="2" equiv-text="${prompt.type}"/>)</source>
<source><x id="0" equiv-text="${prompt.name}"/> ("<x id="1" equiv-text="${prompt.fieldKey}"/>", of type <x id="2" equiv-text="${prompt.type}"/>)</source>
<target>
<x id="0" equiv-text="${prompt.name}"/>(&quot;
<x id="1" equiv-text="${prompt.fieldKey}"/>&quot;, de type
<x id="0" equiv-text="${prompt.name}"/>("
<x id="1" equiv-text="${prompt.fieldKey}"/>", de type
<x id="2" equiv-text="${prompt.type}"/>)</target>
</trans-unit>
@ -5717,8 +5512,8 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit>
<trans-unit id="s1608b2f94fa0dbd4">
<source>If set to a duration above 0, the user will have the option to choose to &quot;stay signed in&quot;, which will extend their session by the time specified here.</source>
<target>Si défini à une durée supérieure à 0, l'utilisateur aura la possibilité de choisir de &quot;rester connecté&quot;, ce qui prolongera sa session jusqu'à la durée spécifiée ici.</target>
<source>If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here.</source>
<target>Si défini à une durée supérieure à 0, l'utilisateur aura la possibilité de choisir de "rester connecté", ce qui prolongera sa session jusqu'à la durée spécifiée ici.</target>
</trans-unit>
<trans-unit id="s542a71bb8f41e057">
@ -6502,7 +6297,7 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
</trans-unit>
<trans-unit id="sa7fcf026bd25f231">
<source>Can be in the format of 'unix://' when connecting to a local docker daemon, using 'ssh://' to connect via SSH, or 'https://:2376' when connecting to a remote system.</source>
<target>Peut être au format &quot;unix://&quot; pour une connexion à un service docker local, &quot;ssh://&quot; pour une connexion via SSH, ou &quot;https://:2376&quot; pour une connexion à un système distant.</target>
<target>Peut être au format "unix://" pour une connexion à un service docker local, "ssh://" pour une connexion via SSH, ou "https://:2376" pour une connexion à un système distant.</target>
</trans-unit>
<trans-unit id="saf1d289e3137c2ea">
@ -7809,7 +7604,7 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
</trans-unit>
<trans-unit id="sff0ac1ace2d90709">
<source>Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a managed outpost, this is done for you).</source>
<target>Utilisez ce fournisseur avec l'option &quot;auth_request&quot; de Nginx ou &quot;forwardAuth&quot; de Traefik. Chaque application/domaine a besoin de son propre fournisseur. De plus, sur chaque domaine, &quot;/outpost.goauthentik.io&quot; doit être routé vers le poste avancé (lorsque vous utilisez un poste avancé géré, cela est fait pour vous).</target>
<target>Utilisez ce fournisseur avec l'option "auth_request" de Nginx ou "forwardAuth" de Traefik. Chaque application/domaine a besoin de son propre fournisseur. De plus, sur chaque domaine, "/outpost.goauthentik.io" doit être routé vers le poste avancé (lorsque vous utilisez un poste avancé géré, cela est fait pour vous).</target>
</trans-unit>
<trans-unit id="scb58b8a60cad8762">
<source>Default relay state</source>
@ -7827,6 +7622,76 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
<source>Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello).</source>
<target>Étape de configuration d'un authentificateur WebAuthn (Yubikey, FaceID/Windows Hello).</target>
</trans-unit>
&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD
<trans-unit id="s1cffe58249b04669">
<source>Internal application name used in URLs.</source>
</trans-unit>
<trans-unit id="sb3d4f79d9d8b71e5">
<source>Submit</source>
</trans-unit>
<trans-unit id="se2b29e6cfe59414c">
<source>UI Settings</source>
</trans-unit>
<trans-unit id="sf45734291c57c606">
<source>OAuth2/OpenID</source>
</trans-unit>
<trans-unit id="s836148f721d8913b">
<source>Transparent Reverse Proxy</source>
</trans-unit>
<trans-unit id="s945a6b94361ee45b">
<source>For transparent reverse proxies with required authentication</source>
</trans-unit>
<trans-unit id="s60e79e1793e6bd56">
<source>Forward Auth Single Application</source>
</trans-unit>
<trans-unit id="sadf073913458acbd">
<source>For nginx's auth_request or traefix's forwardAuth</source>
</trans-unit>
<trans-unit id="s4425a1002597151e">
<source>Forward Auth Domain Level</source>
</trans-unit>
<trans-unit id="se770e9498b3bacf6">
<source>For nginx's auth_request or traefix's forwardAuth per root domain</source>
</trans-unit>
<trans-unit id="s40830ec037f34626">
<source>Configure SAML provider manually</source>
</trans-unit>
<trans-unit id="s154eea2a61d44655">
<source>RADIUS Configuration</source>
</trans-unit>
<trans-unit id="sea9fc40dfd1d18b1">
<source>Configure RADIUS provider manually</source>
</trans-unit>
<trans-unit id="s5a8f04c6ef292743">
<source>SCIM configuration</source>
</trans-unit>
<trans-unit id="sa1b0052ae095b9b3">
<source>Configure SCIM provider manually</source>
</trans-unit>
<trans-unit id="s15831fa50a116545">
<source>Saving Application...</source>
</trans-unit>
<trans-unit id="s823abdb61543a826">
<source>Authentik was unable to save this application:</source>
</trans-unit>
<trans-unit id="s848288f8c2265aad">
<source>Your application has been saved</source>
</trans-unit>
<trans-unit id="sf60f1e5b76897c93">
<source>In the Application:</source>
</trans-unit>
<trans-unit id="s7ce65cf482b7bff0">
<source>In the Provider:</source>
</trans-unit>
<trans-unit id="s67d858051b34c38b">
<source>Method's display Name.</source>
</trans-unit>
<trans-unit id="h10ef80d434185070">
<source>Use this provider with nginx's <x id="0" equiv-text="&lt;code&gt;"/>auth_request<x id="1" equiv-text="&lt;/code&gt;"/> or traefik's
<x id="2" equiv-text="&lt;code&gt;"/>forwardAuth<x id="3" equiv-text="&lt;/code&gt;"/>. Each application/domain needs its own provider.
Additionally, on each domain, <x id="4" equiv-text="&lt;code&gt;"/>/outpost.goauthentik.io<x id="5" equiv-text="&lt;/code&gt;"/> must be
routed to the outpost (when using a managed outpost, this is done for you).</source>
</trans-unit>
<trans-unit id="sd18b18f91b804c3f">
<source>Custom attributes</source>
<target>Attributs personnalisés</target>
@ -7974,6 +7839,9 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
<trans-unit id="s526e2c66bd51ff5f">
<source>Role Info</source>
<target>Informations du rôle</target>
</trans-unit>
<trans-unit id="s2da4aa7a9abeb653">
<source>Pseudolocale (for testing)</source>
</trans-unit>
</body>
</file>

View file

@ -1418,10 +1418,6 @@
<source>Slug</source>
<target>Ślimak</target>
</trans-unit>
<trans-unit id="seb8407fa6607c683">
<source>Internal application name, used in URLs.</source>
<target>Wewnętrzna nazwa aplikacji, używana w adresach URL.</target>
</trans-unit>
<trans-unit id="sdae55084f6cb2662">
<source>Optionally enter a group name. Applications with identical groups are shown grouped together.</source>
<target>Opcjonalnie wprowadź nazwę grupy. Aplikacje z identycznymi grupami są wyświetlane razem.</target>
@ -1433,9 +1429,6 @@
<trans-unit id="s350a616ff5e145ec">
<source>Select a provider that this application should use.</source>
</trans-unit>
<trans-unit id="scd5dff009d1fd847">
<source>Backchannel providers</source>
</trans-unit>
<trans-unit id="s4c6534a118f52fdd">
<source>Select backchannel providers which augment the functionality of the main provider.</source>
</trans-unit>
@ -1722,9 +1715,6 @@
<source>NameID attribute</source>
<target>Atrybut NameID</target>
</trans-unit>
<trans-unit id="sf05c693a6e270b79">
<source>SCIM provider is in preview.</source>
</trans-unit>
<trans-unit id="s2f0f6691de0b0388">
<source>Warning: Provider is not assigned to an application as backchannel provider.</source>
</trans-unit>
@ -1738,38 +1728,10 @@
<source>Run sync again</source>
<target>Uruchom ponownie synchronizację</target>
</trans-unit>
<trans-unit id="sf6a518045ea398f7">
<source>Application details</source>
<target>Szczegóły aplikacji</target>
</trans-unit>
<trans-unit id="s51b0102a7d31d0bf">
<source>Create application</source>
<target>Utwórz aplikację</target>
</trans-unit>
<trans-unit id="sdae3a34cfb38c10f">
<source>Additional UI settings</source>
<target>Dodatkowe ustawienia interfejsu użytkownika</target>
</trans-unit>
<trans-unit id="s233d106d13420dd2">
<source>OAuth2/OIDC</source>
<target>OAuth2/OIDC</target>
</trans-unit>
<trans-unit id="sc2cedfb22488ccb2">
<source>Modern applications, APIs and Single-page applications.</source>
<target>Nowoczesne aplikacje, API i aplikacje jednostronicowe.</target>
</trans-unit>
<trans-unit id="s1d93562422eeac10">
<source>SAML</source>
<target>SAML</target>
</trans-unit>
<trans-unit id="s5923f8a2226a414c">
<source>XML-based SSO standard. Use this if your application only supports SAML.</source>
<target>Standard SSO oparty na XML. Użyj tego, jeśli Twoja aplikacja obsługuje tylko SAML.</target>
</trans-unit>
<trans-unit id="s22f7a7ec3bc2c118">
<source>Legacy applications which don't natively support SSO.</source>
<target>Starsze aplikacje, które nie obsługują natywnego logowania jednokrotnego.</target>
</trans-unit>
<trans-unit id="sc3259eb55cf91e8c">
<source>LDAP</source>
<target>LDAP</target>
@ -1777,128 +1739,10 @@
<trans-unit id="sffd5481034a1bd41">
<source>Provide an LDAP interface for applications and users to authenticate against.</source>
</trans-unit>
<trans-unit id="s3cf89cb47fdde7e9">
<source>Link</source>
<target>Link</target>
</trans-unit>
<trans-unit id="s7ccc013f41f0a2c2">
<source>Authentication method</source>
<target>Metoda Uwierzytelnienia</target>
</trans-unit>
<trans-unit id="sd45fc6453f617152">
<source>LDAP details</source>
<target>Szczegóły LDAP</target>
</trans-unit>
<trans-unit id="scf72690719b7a8a1">
<source>Create service account</source>
<target>Utwórz konto usługi</target>
</trans-unit>
<trans-unit id="sbda06adbba493976">
<source>Create provider</source>
<target>Utwórz dostawcę</target>
</trans-unit>
<trans-unit id="s12d0cb01d9f6f3b3">
<source>Application Link</source>
<target>Link do aplikacji</target>
</trans-unit>
<trans-unit id="s54a13196915fd76c">
<source>URL which will be opened when a user clicks on the application.</source>
</trans-unit>
<trans-unit id="sa34866db5e891cc6">
<source>Method details</source>
<target>Szczegóły metody</target>
</trans-unit>
<trans-unit id="saf840955862a8cd0">
<source>This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically.</source>
</trans-unit>
<trans-unit id="sb2f3a45baaa52dfd">
<source>By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password.</source>
</trans-unit>
<trans-unit id="se0df976add086ec3">
<source>Web application</source>
<target>Aplikacja internetowa</target>
</trans-unit>
<trans-unit id="s042fb9b0f9acc21f">
<source>Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP)</source>
<target>Aplikacje obsługujące uwierzytelnianie po stronie serwera (na przykład Python, Go, Rust, Java, PHP)</target>
</trans-unit>
<trans-unit id="sdf4bd51375026222">
<source>Single-page applications</source>
<target>Aplikacje jednostronicowe</target>
</trans-unit>
<trans-unit id="s7fa3c3306b77187d">
<source>Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue)</source>
</trans-unit>
<trans-unit id="s3db92162f63a12b4">
<source>Native application</source>
<target>Natywna aplikacja</target>
</trans-unit>
<trans-unit id="s2f43274a59b0d384">
<source>Applications which redirect users to a non-web callback (for example, Android, iOS)</source>
<target>Aplikacje, które przekierowują użytkowników do nie internetowych callback (na przykład Android, iOS)</target>
</trans-unit>
<trans-unit id="sfa5f0019a09be6a7">
<source>API</source>
<target>API</target>
</trans-unit>
<trans-unit id="s4d53cad956dfc4e4">
<source>Authentication without user interaction, or machine-to-machine authentication.</source>
<target>Uwierzytelnianie bez interakcji użytkownika lub uwierzytelnianie między maszynami.</target>
</trans-unit>
<trans-unit id="s134083b4613bcf87">
<source>Application type</source>
<target>Typ aplikacji</target>
</trans-unit>
<trans-unit id="s649e7d74f27635f8">
<source>Flow used when users access this application.</source>
<target>Przepływ używany, gdy użytkownicy uzyskują dostęp do tej aplikacji.</target>
</trans-unit>
<trans-unit id="s9c1422a7e3d5c619">
<source>Proxy details</source>
<target>Dane proxy</target>
</trans-unit>
<trans-unit id="s7714fdf13df8b682">
<source>External domain</source>
<target>Zewnętrzna domena</target>
</trans-unit>
<trans-unit id="sf5a280bdcc775c49">
<source>External domain you will be accessing the domain from.</source>
<target>Domena zewnętrzna, z której będziesz uzyskiwać dostęp do domeny.</target>
</trans-unit>
<trans-unit id="sb11e750b3de9bb72">
<source>Import SAML Metadata</source>
<target>Importuj Metadane SAML</target>
</trans-unit>
<trans-unit id="s0955bd1d1b1dc710">
<source>Import the metadata document of the applicaation you want to configure.</source>
</trans-unit>
<trans-unit id="s0be509fcae72af45">
<source>Manual configuration</source>
<target>Ręczna konfiguracja</target>
</trans-unit>
<trans-unit id="s485b90ede24fdb19">
<source>Manually configure SAML</source>
<target>Ręcznie skonfiguruj SAML</target>
</trans-unit>
<trans-unit id="se7d73f906d358a46">
<source>SAML details</source>
<target>Szczegóły SAML</target>
</trans-unit>
<trans-unit id="s982a7cdc93aa6fbe">
<source>URL that authentik will redirect back to after successful authentication.</source>
</trans-unit>
<trans-unit id="sc9e75f44b775aa52">
<source>Import SAML metadata</source>
<target>Importuj metadane SAML</target>
</trans-unit>
<trans-unit id="s0c9670f429e74283">
<source>New application</source>
<target>Nowa aplikacja</target>
</trans-unit>
<trans-unit id="s8e1d308c09d1b400">
<source>Create a new application.</source>
<target>Utwórz nową aplikację</target>
</trans-unit>
<trans-unit id="s6ba50bb0842ba1e2">
<source>Applications</source>
<target>Aplikacje</target>
@ -6080,6 +5924,76 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s24bce955914b1f0a">
<source>Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello).</source>
</trans-unit>
&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD
<trans-unit id="s1cffe58249b04669">
<source>Internal application name used in URLs.</source>
</trans-unit>
<trans-unit id="sb3d4f79d9d8b71e5">
<source>Submit</source>
</trans-unit>
<trans-unit id="se2b29e6cfe59414c">
<source>UI Settings</source>
</trans-unit>
<trans-unit id="sf45734291c57c606">
<source>OAuth2/OpenID</source>
</trans-unit>
<trans-unit id="s836148f721d8913b">
<source>Transparent Reverse Proxy</source>
</trans-unit>
<trans-unit id="s945a6b94361ee45b">
<source>For transparent reverse proxies with required authentication</source>
</trans-unit>
<trans-unit id="s60e79e1793e6bd56">
<source>Forward Auth Single Application</source>
</trans-unit>
<trans-unit id="sadf073913458acbd">
<source>For nginx's auth_request or traefix's forwardAuth</source>
</trans-unit>
<trans-unit id="s4425a1002597151e">
<source>Forward Auth Domain Level</source>
</trans-unit>
<trans-unit id="se770e9498b3bacf6">
<source>For nginx's auth_request or traefix's forwardAuth per root domain</source>
</trans-unit>
<trans-unit id="s40830ec037f34626">
<source>Configure SAML provider manually</source>
</trans-unit>
<trans-unit id="s154eea2a61d44655">
<source>RADIUS Configuration</source>
</trans-unit>
<trans-unit id="sea9fc40dfd1d18b1">
<source>Configure RADIUS provider manually</source>
</trans-unit>
<trans-unit id="s5a8f04c6ef292743">
<source>SCIM configuration</source>
</trans-unit>
<trans-unit id="sa1b0052ae095b9b3">
<source>Configure SCIM provider manually</source>
</trans-unit>
<trans-unit id="s15831fa50a116545">
<source>Saving Application...</source>
</trans-unit>
<trans-unit id="s823abdb61543a826">
<source>Authentik was unable to save this application:</source>
</trans-unit>
<trans-unit id="s848288f8c2265aad">
<source>Your application has been saved</source>
</trans-unit>
<trans-unit id="sf60f1e5b76897c93">
<source>In the Application:</source>
</trans-unit>
<trans-unit id="s7ce65cf482b7bff0">
<source>In the Provider:</source>
</trans-unit>
<trans-unit id="s67d858051b34c38b">
<source>Method's display Name.</source>
</trans-unit>
<trans-unit id="h10ef80d434185070">
<source>Use this provider with nginx's <x id="0" equiv-text="&lt;code&gt;"/>auth_request<x id="1" equiv-text="&lt;/code&gt;"/> or traefik's
<x id="2" equiv-text="&lt;code&gt;"/>forwardAuth<x id="3" equiv-text="&lt;/code&gt;"/>. Each application/domain needs its own provider.
Additionally, on each domain, <x id="4" equiv-text="&lt;code&gt;"/>/outpost.goauthentik.io<x id="5" equiv-text="&lt;/code&gt;"/> must be
routed to the outpost (when using a managed outpost, this is done for you).</source>
</trans-unit>
<trans-unit id="sd18b18f91b804c3f">
<source>Custom attributes</source>
</trans-unit>
@ -6190,6 +6104,9 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s526e2c66bd51ff5f">
<source>Role Info</source>
</trans-unit>
<trans-unit id="s2da4aa7a9abeb653">
<source>Pseudolocale (for testing)</source>
</trans-unit>
</body>
</file>

View file

@ -1817,11 +1817,6 @@
<source>Slug</source>
<target>Śĺũĝ</target>
</trans-unit>
<trans-unit id="seb8407fa6607c683">
<source>Internal application name, used in URLs.</source>
<target>Ĩńţēŕńàĺ àƥƥĺĩćàţĩōń ńàmē, ũśēď ĩń ŨŔĹś.</target>
</trans-unit>
<trans-unit id="sdae55084f6cb2662">
<source>Optionally enter a group name. Applications with identical groups are shown grouped together.</source>
@ -1837,11 +1832,6 @@
<source>Select a provider that this application should use.</source>
<target>Śēĺēćţ à ƥŕōvĩďēŕ ţĥàţ ţĥĩś àƥƥĺĩćàţĩōń śĥōũĺď ũśē.</target>
</trans-unit>
<trans-unit id="scd5dff009d1fd847">
<source>Backchannel providers</source>
<target>ßàćķćĥàńńēĺ ƥŕōvĩďēŕś</target>
</trans-unit>
<trans-unit id="s4c6534a118f52fdd">
<source>Select backchannel providers which augment the functionality of the main provider.</source>
@ -2206,7 +2196,6 @@
<trans-unit id="sea3bfc143ced73db">
<source>NameID attribute</source>
<target>ŃàmēĨĎ àţţŕĩƀũţē</target>
</trans-unit>
<trans-unit id="s2f0f6691de0b0388">
<source>Warning: Provider is not assigned to an application as backchannel provider.</source>
@ -2227,46 +2216,11 @@
<source>Run sync again</source>
<target>Ŕũń śŷńć àĝàĩń</target>
</trans-unit>
<trans-unit id="sf6a518045ea398f7">
<source>Application details</source>
<target>Àƥƥĺĩćàţĩōń ďēţàĩĺś</target>
</trans-unit>
<trans-unit id="s51b0102a7d31d0bf">
<source>Create application</source>
<target>Ćŕēàţē àƥƥĺĩćàţĩōń</target>
</trans-unit>
<trans-unit id="sdae3a34cfb38c10f">
<source>Additional UI settings</source>
<target>Àďďĩţĩōńàĺ ŨĨ śēţţĩńĝś</target>
</trans-unit>
<trans-unit id="s233d106d13420dd2">
<source>OAuth2/OIDC</source>
<target>ŌÀũţĥ2/ŌĨĎĆ</target>
</trans-unit>
<trans-unit id="sc2cedfb22488ccb2">
<source>Modern applications, APIs and Single-page applications.</source>
<target>Mōďēŕń àƥƥĺĩćàţĩōńś, ÀƤĨś àńď Śĩńĝĺē-ƥàĝē àƥƥĺĩćàţĩōńś.</target>
</trans-unit>
<trans-unit id="s1d93562422eeac10">
<source>SAML</source>
<target>ŚÀMĹ</target>
</trans-unit>
<trans-unit id="s5923f8a2226a414c">
<source>XML-based SSO standard. Use this if your application only supports SAML.</source>
<target>XMĹ-ƀàśēď ŚŚŌ śţàńďàŕď. Ũśē ţĥĩś ĩƒ ŷōũŕ àƥƥĺĩćàţĩōń ōńĺŷ śũƥƥōŕţś ŚÀMĹ.</target>
</trans-unit>
<trans-unit id="s22f7a7ec3bc2c118">
<source>Legacy applications which don't natively support SSO.</source>
<target>Ĺēĝàćŷ àƥƥĺĩćàţĩōńś ŵĥĩćĥ ďōń'ţ ńàţĩvēĺŷ śũƥƥōŕţ ŚŚŌ.</target>
</trans-unit>
<trans-unit id="sc3259eb55cf91e8c">
<source>LDAP</source>
@ -2277,166 +2231,11 @@
<source>Provide an LDAP interface for applications and users to authenticate against.</source>
<target>Ƥŕōvĩďē àń ĹĎÀƤ ĩńţēŕƒàćē ƒōŕ àƥƥĺĩćàţĩōńś àńď ũśēŕś ţō àũţĥēńţĩćàţē àĝàĩńśţ.</target>
</trans-unit>
<trans-unit id="s3cf89cb47fdde7e9">
<source>Link</source>
<target>Ĺĩńķ</target>
</trans-unit>
<trans-unit id="s7ccc013f41f0a2c2">
<source>Authentication method</source>
<target>Àũţĥēńţĩćàţĩōń mēţĥōď</target>
</trans-unit>
<trans-unit id="sd45fc6453f617152">
<source>LDAP details</source>
<target>ĹĎÀƤ ďēţàĩĺś</target>
</trans-unit>
<trans-unit id="scf72690719b7a8a1">
<source>Create service account</source>
<target>Ćŕēàţē śēŕvĩćē àććōũńţ</target>
</trans-unit>
<trans-unit id="sbda06adbba493976">
<source>Create provider</source>
<target>Ćŕēàţē ƥŕōvĩďēŕ</target>
</trans-unit>
<trans-unit id="s12d0cb01d9f6f3b3">
<source>Application Link</source>
<target>Àƥƥĺĩćàţĩōń Ĺĩńķ</target>
</trans-unit>
<trans-unit id="s54a13196915fd76c">
<source>URL which will be opened when a user clicks on the application.</source>
<target>ŨŔĹ ŵĥĩćĥ ŵĩĺĺ ƀē ōƥēńēď ŵĥēń à ũśēŕ ćĺĩćķś ōń ţĥē àƥƥĺĩćàţĩōń.</target>
</trans-unit>
<trans-unit id="sa34866db5e891cc6">
<source>Method details</source>
<target>Mēţĥōď ďēţàĩĺś</target>
</trans-unit>
<trans-unit id="saf840955862a8cd0">
<source>This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically.</source>
<target>Ţĥĩś ćōńƒĩĝũŕàţĩōń ćàń ƀē ũśēď ţō àũţĥēńţĩćàţē ţō àũţĥēńţĩķ ŵĩţĥ ōţĥēŕ ÀƤĨś ōţĥēŕ ōţĥēŕŵĩśē ƥŕōĝŕàmmàţĩćàĺĺŷ.</target>
</trans-unit>
<trans-unit id="sb2f3a45baaa52dfd">
<source>By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password.</source>
<target>ßŷ ďēƒàũĺţ, àĺĺ śēŕvĩćē àććōũńţś ćàń àũţĥēńţĩćàţē àś ţĥĩś àƥƥĺĩćàţĩōń, àś ĺōńĝ àś ţĥēŷ ĥàvē à vàĺĩď ţōķēń ōƒ ţĥē ţŷƥē àƥƥ-ƥàśśŵōŕď.</target>
</trans-unit>
<trans-unit id="se0df976add086ec3">
<source>Web application</source>
<target>Ŵēƀ àƥƥĺĩćàţĩōń</target>
</trans-unit>
<trans-unit id="s042fb9b0f9acc21f">
<source>Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP)</source>
<target>Àƥƥĺĩćàţĩōńś ŵĥĩćĥ ĥàńďĺē ţĥē àũţĥēńţĩćàţĩōń śēŕvēŕ-śĩďē (ƒōŕ ēxàmƥĺē, Ƥŷţĥōń, Ĝō, Ŕũśţ, ĵàvà, ƤĤƤ)</target>
</trans-unit>
<trans-unit id="sdf4bd51375026222">
<source>Single-page applications</source>
<target>Śĩńĝĺē-ƥàĝē àƥƥĺĩćàţĩōńś</target>
</trans-unit>
<trans-unit id="s7fa3c3306b77187d">
<source>Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue)</source>
<target>Śĩńĝĺē-ƥàĝē àƥƥĺĩćàţĩōńś ŵĥĩćĥ ĥàńďĺē àũţĥēńţĩćàţĩōń ĩń ţĥē ƀŕōŵśēŕ (ƒōŕ ēxàmƥĺē, ĵàvàśćŕĩƥţ, Àńĝũĺàŕ, Ŕēàćţ, Vũē)</target>
</trans-unit>
<trans-unit id="s3db92162f63a12b4">
<source>Native application</source>
<target>Ńàţĩvē àƥƥĺĩćàţĩōń</target>
</trans-unit>
<trans-unit id="s2f43274a59b0d384">
<source>Applications which redirect users to a non-web callback (for example, Android, iOS)</source>
<target>Àƥƥĺĩćàţĩōńś ŵĥĩćĥ ŕēďĩŕēćţ ũśēŕś ţō à ńōń-ŵēƀ ćàĺĺƀàćķ (ƒōŕ ēxàmƥĺē, Àńďŕōĩď, ĩŌŚ)</target>
</trans-unit>
<trans-unit id="sfa5f0019a09be6a7">
<source>API</source>
<target>ÀƤĨ</target>
</trans-unit>
<trans-unit id="s4d53cad956dfc4e4">
<source>Authentication without user interaction, or machine-to-machine authentication.</source>
<target>Àũţĥēńţĩćàţĩōń ŵĩţĥōũţ ũśēŕ ĩńţēŕàćţĩōń, ōŕ màćĥĩńē-ţō-màćĥĩńē àũţĥēńţĩćàţĩōń.</target>
</trans-unit>
<trans-unit id="s134083b4613bcf87">
<source>Application type</source>
<target>Àƥƥĺĩćàţĩōń ţŷƥē</target>
</trans-unit>
<trans-unit id="s649e7d74f27635f8">
<source>Flow used when users access this application.</source>
<target>Ƒĺōŵ ũśēď ŵĥēń ũśēŕś àććēśś ţĥĩś àƥƥĺĩćàţĩōń.</target>
</trans-unit>
<trans-unit id="s9c1422a7e3d5c619">
<source>Proxy details</source>
<target>Ƥŕōxŷ ďēţàĩĺś</target>
</trans-unit>
<trans-unit id="s7714fdf13df8b682">
<source>External domain</source>
<target>Ēxţēŕńàĺ ďōmàĩń</target>
</trans-unit>
<trans-unit id="sf5a280bdcc775c49">
<source>External domain you will be accessing the domain from.</source>
<target>Ēxţēŕńàĺ ďōmàĩń ŷōũ ŵĩĺĺ ƀē àććēśśĩńĝ ţĥē ďōmàĩń ƒŕōm.</target>
</trans-unit>
<trans-unit id="sb11e750b3de9bb72">
<source>Import SAML Metadata</source>
<target>Ĩmƥōŕţ ŚÀMĹ Mēţàďàţà</target>
</trans-unit>
<trans-unit id="s0955bd1d1b1dc710">
<source>Import the metadata document of the applicaation you want to configure.</source>
<target>Ĩmƥōŕţ ţĥē mēţàďàţà ďōćũmēńţ ōƒ ţĥē àƥƥĺĩćààţĩōń ŷōũ ŵàńţ ţō ćōńƒĩĝũŕē.</target>
</trans-unit>
<trans-unit id="s0be509fcae72af45">
<source>Manual configuration</source>
<target>Màńũàĺ ćōńƒĩĝũŕàţĩōń</target>
</trans-unit>
<trans-unit id="s485b90ede24fdb19">
<source>Manually configure SAML</source>
<target>Màńũàĺĺŷ ćōńƒĩĝũŕē ŚÀMĹ</target>
</trans-unit>
<trans-unit id="se7d73f906d358a46">
<source>SAML details</source>
<target>ŚÀMĹ ďēţàĩĺś</target>
</trans-unit>
<trans-unit id="s982a7cdc93aa6fbe">
<source>URL that authentik will redirect back to after successful authentication.</source>
<target>ŨŔĹ ţĥàţ àũţĥēńţĩķ ŵĩĺĺ ŕēďĩŕēćţ ƀàćķ ţō àƒţēŕ śũććēśśƒũĺ àũţĥēńţĩćàţĩōń.</target>
</trans-unit>
<trans-unit id="sc9e75f44b775aa52">
<source>Import SAML metadata</source>
<target>Ĩmƥōŕţ ŚÀMĹ mēţàďàţà</target>
</trans-unit>
<trans-unit id="s0c9670f429e74283">
<source>New application</source>
<target>Ńēŵ àƥƥĺĩćàţĩōń</target>
</trans-unit>
<trans-unit id="s8e1d308c09d1b400">
<source>Create a new application.</source>
<target>Ćŕēàţē à ńēŵ àƥƥĺĩćàţĩōń.</target>
</trans-unit>
<trans-unit id="s6ba50bb0842ba1e2">
<source>Applications</source>
@ -7762,6 +7561,101 @@ Bindings to groups/users are checked against the user of the event.</source>
<source>Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello).</source>
<target>Śţàĝē ũśēď ţō ćōńƒĩĝũŕē à ŴēƀÀũţĥń àũţĥēńţĩćàţōŕ (ĩ.ē. Ŷũƀĩķēŷ, ƑàćēĨĎ/Ŵĩńďōŵś Ĥēĺĺō).</target>
</trans-unit>
&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD
<trans-unit id="s1cffe58249b04669">
<source>Internal application name used in URLs.</source>
<target>Ĩńţēŕńàĺ àƥƥĺĩćàţĩōń ńàmē ũśēď ĩń ŨŔĹś.</target>
</trans-unit>
<trans-unit id="sb3d4f79d9d8b71e5">
<source>Submit</source>
<target>Śũƀmĩţ</target>
</trans-unit>
<trans-unit id="se2b29e6cfe59414c">
<source>UI Settings</source>
<target>ŨĨ Śēţţĩńĝś</target>
</trans-unit>
<trans-unit id="sf45734291c57c606">
<source>OAuth2/OpenID</source>
<target>ŌÀũţĥ2/ŌƥēńĨĎ</target>
</trans-unit>
<trans-unit id="s836148f721d8913b">
<source>Transparent Reverse Proxy</source>
<target>Ţŕàńśƥàŕēńţ Ŕēvēŕśē Ƥŕōxŷ</target>
</trans-unit>
<trans-unit id="s945a6b94361ee45b">
<source>For transparent reverse proxies with required authentication</source>
<target>Ƒōŕ ţŕàńśƥàŕēńţ ŕēvēŕśē ƥŕōxĩēś ŵĩţĥ ŕēǫũĩŕēď àũţĥēńţĩćàţĩōń</target>
</trans-unit>
<trans-unit id="s60e79e1793e6bd56">
<source>Forward Auth Single Application</source>
<target>Ƒōŕŵàŕď Àũţĥ Śĩńĝĺē Àƥƥĺĩćàţĩōń</target>
</trans-unit>
<trans-unit id="sadf073913458acbd">
<source>For nginx's auth_request or traefix's forwardAuth</source>
<target>Ƒōŕ ńĝĩńx'ś àũţĥ_ŕēǫũēśţ ōŕ ţŕàēƒĩx'ś ƒōŕŵàŕďÀũţĥ</target>
</trans-unit>
<trans-unit id="s4425a1002597151e">
<source>Forward Auth Domain Level</source>
<target>Ƒōŕŵàŕď Àũţĥ Ďōmàĩń Ĺēvēĺ</target>
</trans-unit>
<trans-unit id="se770e9498b3bacf6">
<source>For nginx's auth_request or traefix's forwardAuth per root domain</source>
<target>Ƒōŕ ńĝĩńx'ś àũţĥ_ŕēǫũēśţ ōŕ ţŕàēƒĩx'ś ƒōŕŵàŕďÀũţĥ ƥēŕ ŕōōţ ďōmàĩń</target>
</trans-unit>
<trans-unit id="s40830ec037f34626">
<source>Configure SAML provider manually</source>
<target>Ćōńƒĩĝũŕē ŚÀMĹ ƥŕōvĩďēŕ màńũàĺĺŷ</target>
</trans-unit>
<trans-unit id="s154eea2a61d44655">
<source>RADIUS Configuration</source>
<target>ŔÀĎĨŨŚ Ćōńƒĩĝũŕàţĩōń</target>
</trans-unit>
<trans-unit id="sea9fc40dfd1d18b1">
<source>Configure RADIUS provider manually</source>
<target>Ćōńƒĩĝũŕē ŔÀĎĨŨŚ ƥŕōvĩďēŕ màńũàĺĺŷ</target>
</trans-unit>
<trans-unit id="s5a8f04c6ef292743">
<source>SCIM configuration</source>
<target>ŚĆĨM ćōńƒĩĝũŕàţĩōń</target>
</trans-unit>
<trans-unit id="sa1b0052ae095b9b3">
<source>Configure SCIM provider manually</source>
<target>Ćōńƒĩĝũŕē ŚĆĨM ƥŕōvĩďēŕ màńũàĺĺŷ</target>
</trans-unit>
<trans-unit id="s15831fa50a116545">
<source>Saving Application...</source>
<target>Śàvĩńĝ Àƥƥĺĩćàţĩōń...</target>
</trans-unit>
<trans-unit id="s823abdb61543a826">
<source>Authentik was unable to save this application:</source>
<target>Àũţĥēńţĩķ ŵàś ũńàƀĺē ţō śàvē ţĥĩś àƥƥĺĩćàţĩōń:</target>
</trans-unit>
<trans-unit id="s848288f8c2265aad">
<source>Your application has been saved</source>
<target>Ŷōũŕ àƥƥĺĩćàţĩōń ĥàś ƀēēń śàvēď</target>
</trans-unit>
<trans-unit id="sf60f1e5b76897c93">
<source>In the Application:</source>
<target>Ĩń ţĥē Àƥƥĺĩćàţĩōń:</target>
</trans-unit>
<trans-unit id="s7ce65cf482b7bff0">
<source>In the Provider:</source>
<target>Ĩń ţĥē Ƥŕōvĩďēŕ:</target>
</trans-unit>
<trans-unit id="s67d858051b34c38b">
<source>Method's display Name.</source>
<target>Mēţĥōď'ś ďĩśƥĺàŷ Ńàmē.</target>
</trans-unit>
<trans-unit id="h10ef80d434185070">
<source>Use this provider with nginx's <x id="0" equiv-text="&lt;code&gt;"/>auth_request<x id="1" equiv-text="&lt;/code&gt;"/> or traefik's
<x id="2" equiv-text="&lt;code&gt;"/>forwardAuth<x id="3" equiv-text="&lt;/code&gt;"/>. Each application/domain needs its own provider.
Additionally, on each domain, <x id="4" equiv-text="&lt;code&gt;"/>/outpost.goauthentik.io<x id="5" equiv-text="&lt;/code&gt;"/> must be
routed to the outpost (when using a managed outpost, this is done for you).</source>
<target>Ũśē ţĥĩś ƥŕōvĩďēŕ ŵĩţĥ ńĝĩńx'ś <x id="0" equiv-text="&lt;code&gt;"/>àũţĥ_ŕēǫũēśţ<x id="1" equiv-text="&lt;/code&gt;"/> ōŕ ţŕàēƒĩķ'ś
<x id="2" equiv-text="&lt;code&gt;"/>ƒōŕŵàŕďÀũţĥ<x id="3" equiv-text="&lt;/code&gt;"/>. Ēàćĥ àƥƥĺĩćàţĩōń/ďōmàĩń ńēēďś ĩţś ōŵń ƥŕōvĩďēŕ.
Àďďĩţĩōńàĺĺŷ, ōń ēàćĥ ďōmàĩń, <x id="4" equiv-text="&lt;code&gt;"/>/ōũţƥōśţ.ĝōàũţĥēńţĩķ.ĩō<x id="5" equiv-text="&lt;/code&gt;"/> mũśţ ƀē
ŕōũţēď ţō ţĥē ōũţƥōśţ (ŵĥēń ũśĩńĝ à màńàĝēď ōũţƥōśţ, ţĥĩś ĩś ďōńē ƒōŕ ŷōũ).</target>
</trans-unit>
<trans-unit id="sd18b18f91b804c3f">
<source>Custom attributes</source>
<target>Ćũśţōm àţţŕĩƀũţēś</target>
@ -7776,109 +7670,142 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s3e99ea082ca5ade9">
<source>Failed to fetch</source>
<target>Ƒàĩĺēď ţō ƒēţćĥ</target>
</trans-unit>
<trans-unit id="s98327528f00365a7">
<source>Failed to fetch data.</source>
<target>Ƒàĩĺēď ţō ƒēţćĥ ďàţà.</target>
</trans-unit>
<trans-unit id="sf485014051ad0cf7">
<source>Successfully assigned permission.</source>
<target>Śũććēśśƒũĺĺŷ àśśĩĝńēď ƥēŕmĩśśĩōń.</target>
</trans-unit>
<trans-unit id="sca7fed2bef53cb99">
<source>Role</source>
<target>Ŕōĺē</target>
</trans-unit>
<trans-unit id="sc92c1a54034e21cc">
<source>Assign</source>
<target>Àśśĩĝń</target>
</trans-unit>
<trans-unit id="scd84d10ee9137070">
<source>Assign permission to role</source>
<target>Àśśĩĝń ƥēŕmĩśśĩōń ţō ŕōĺē</target>
</trans-unit>
<trans-unit id="s5ee6f1b84e9ebc69">
<source>Assign to new role</source>
<target>Àśśĩĝń ţō ńēŵ ŕōĺē</target>
</trans-unit>
<trans-unit id="s4afb26a8fae257e9">
<source>Directly assigned</source>
<target>Ďĩŕēćţĺŷ àśśĩĝńēď</target>
</trans-unit>
<trans-unit id="sd8051c26e155f043">
<source>Assign permission to user</source>
<target>Àśśĩĝń ƥēŕmĩśśĩōń ţō ũśēŕ</target>
</trans-unit>
<trans-unit id="sf79f8681e5ffaee2">
<source>Assign to new user</source>
<target>Àśśĩĝń ţō ńēŵ ũśēŕ</target>
</trans-unit>
<trans-unit id="saabeb4cab074b0b9">
<source>User Object Permissions</source>
<target>Ũśēŕ ŌƀĴēćţ Ƥēŕmĩśśĩōńś</target>
</trans-unit>
<trans-unit id="s8489d5559dda260c">
<source>Role Object Permissions</source>
<target>Ŕōĺē ŌƀĴēćţ Ƥēŕmĩśśĩōńś</target>
</trans-unit>
<trans-unit id="s6b2beba7ab637e9e">
<source>Roles</source>
<target>Ŕōĺēś</target>
</trans-unit>
<trans-unit id="s96d2bb4be3f5e8aa">
<source>Select roles to grant this groups' users' permissions from the selected roles.</source>
<target>Śēĺēćţ ŕōĺēś ţō ĝŕàńţ ţĥĩś ĝŕōũƥś' ũśēŕś' ƥēŕmĩśśĩōńś ƒŕōm ţĥē śēĺēćţēď ŕōĺēś.</target>
</trans-unit>
<trans-unit id="sb37880a2a7288ef0">
<source>Update Permissions</source>
<target>Ũƥďàţē Ƥēŕmĩśśĩōńś</target>
</trans-unit>
<trans-unit id="se9c07cf256774d81">
<source>Editing is disabled for managed tokens</source>
<target>Ēďĩţĩńĝ ĩś ďĩśàƀĺēď ƒōŕ màńàĝēď ţōķēńś</target>
</trans-unit>
<trans-unit id="s78ab26da7f067de8">
<source>Select permissions to grant</source>
<target>Śēĺēćţ ƥēŕmĩśśĩōńś ţō ĝŕàńţ</target>
</trans-unit>
<trans-unit id="sdeb90bfd8a80b86b">
<source>Permissions to add</source>
<target>Ƥēŕmĩśśĩōńś ţō àďď</target>
</trans-unit>
<trans-unit id="s36247910d67421e1">
<source>Select permissions</source>
<target>Śēĺēćţ ƥēŕmĩśśĩōńś</target>
</trans-unit>
<trans-unit id="s67e136af8fc1107b">
<source>Assign permission</source>
<target>Àśśĩĝń ƥēŕmĩśśĩōń</target>
</trans-unit>
<trans-unit id="sb923723d27df40ba">
<source>Permission(s)</source>
<target>Ƥēŕmĩśśĩōń(ś)</target>
</trans-unit>
<trans-unit id="sc5fb00b25c7f5a02">
<source>Permission</source>
<target>Ƥēŕmĩśśĩōń</target>
</trans-unit>
<trans-unit id="s1455753daa00f1bc">
<source>User doesn't have view permission so description cannot be retrieved.</source>
<target>Ũśēŕ ďōēśń'ţ ĥàvē vĩēŵ ƥēŕmĩśśĩōń śō ďēśćŕĩƥţĩōń ćàńńōţ ƀē ŕēţŕĩēvēď.</target>
</trans-unit>
<trans-unit id="sa3a3e09b88ed9791">
<source>Assigned permissions</source>
<target>Àśśĩĝńēď ƥēŕmĩśśĩōńś</target>
</trans-unit>
<trans-unit id="s9cc631505c17b028">
<source>Assigned global permissions</source>
<target>Àśśĩĝńēď ĝĺōƀàĺ ƥēŕmĩśśĩōńś</target>
</trans-unit>
<trans-unit id="s8f85a0e678846080">
<source>Assigned object permissions</source>
<target>Àśśĩĝńēď ōƀĴēćţ ƥēŕmĩśśĩōńś</target>
</trans-unit>
<trans-unit id="s9103a949a3963aa9">
<source>Successfully updated role.</source>
<target>Śũććēśśƒũĺĺŷ ũƥďàţēď ŕōĺē.</target>
</trans-unit>
<trans-unit id="sdf87c5661b31359e">
<source>Successfully created role.</source>
<target>Śũććēśśƒũĺĺŷ ćŕēàţēď ŕōĺē.</target>
</trans-unit>
<trans-unit id="s3484b1e6d0b5335f">
<source>Manage roles which grant permissions to objects within authentik.</source>
<target>Màńàĝē ŕōĺēś ŵĥĩćĥ ĝŕàńţ ƥēŕmĩśśĩōńś ţō ōƀĴēćţś ŵĩţĥĩń àũţĥēńţĩķ.</target>
</trans-unit>
<trans-unit id="s259de999919316db">
<source>Role(s)</source>
<target>Ŕōĺē(ś)</target>
</trans-unit>
<trans-unit id="s2ffad156e8332f04">
<source>Update Role</source>
<target>Ũƥďàţē Ŕōĺē</target>
</trans-unit>
<trans-unit id="sc5f923729564fbf3">
<source>Create Role</source>
<target>Ćŕēàţē Ŕōĺē</target>
</trans-unit>
<trans-unit id="s14bfa8fd1bec8889">
<source>Role doesn't have view permission so description cannot be retrieved.</source>
<target>Ŕōĺē ďōēśń'ţ ĥàvē vĩēŵ ƥēŕmĩśśĩōń śō ďēśćŕĩƥţĩōń ćàńńōţ ƀē ŕēţŕĩēvēď.</target>
</trans-unit>
<trans-unit id="s7e796fe83982863f">
<source>Role <x id="0" equiv-text="${this._role?.name || &quot;&quot;}"/></source>
<target>Ŕōĺē <x id="0" equiv-text="${this._role?.name || &quot;&quot;}"/></target>
</trans-unit>
<trans-unit id="s526e2c66bd51ff5f">
<source>Role Info</source>
<target>Ŕōĺē Ĩńƒō</target>
</trans-unit>
</body>
</file>
</xliff>
</body></file></xliff>

View file

@ -1377,10 +1377,6 @@
<source>Slug</source>
<target>Kısa İsim</target>
</trans-unit>
<trans-unit id="seb8407fa6607c683">
<source>Internal application name, used in URLs.</source>
<target>URL'lerde kullanılan dahili uygulama adı.</target>
</trans-unit>
<trans-unit id="sdae55084f6cb2662">
<source>Optionally enter a group name. Applications with identical groups are shown grouped together.</source>
</trans-unit>
@ -1391,9 +1387,6 @@
<trans-unit id="s350a616ff5e145ec">
<source>Select a provider that this application should use.</source>
</trans-unit>
<trans-unit id="scd5dff009d1fd847">
<source>Backchannel providers</source>
</trans-unit>
<trans-unit id="s4c6534a118f52fdd">
<source>Select backchannel providers which augment the functionality of the main provider.</source>
</trans-unit>
@ -1667,9 +1660,6 @@
<trans-unit id="sea3bfc143ced73db">
<source>NameID attribute</source>
</trans-unit>
<trans-unit id="sf05c693a6e270b79">
<source>SCIM provider is in preview.</source>
</trans-unit>
<trans-unit id="s2f0f6691de0b0388">
<source>Warning: Provider is not assigned to an application as backchannel provider.</source>
</trans-unit>
@ -1683,30 +1673,9 @@
<source>Run sync again</source>
<target>Eşzamanlamayı tekrar çalıştır</target>
</trans-unit>
<trans-unit id="sf6a518045ea398f7">
<source>Application details</source>
</trans-unit>
<trans-unit id="s51b0102a7d31d0bf">
<source>Create application</source>
</trans-unit>
<trans-unit id="sdae3a34cfb38c10f">
<source>Additional UI settings</source>
</trans-unit>
<trans-unit id="s233d106d13420dd2">
<source>OAuth2/OIDC</source>
</trans-unit>
<trans-unit id="sc2cedfb22488ccb2">
<source>Modern applications, APIs and Single-page applications.</source>
</trans-unit>
<trans-unit id="s1d93562422eeac10">
<source>SAML</source>
</trans-unit>
<trans-unit id="s5923f8a2226a414c">
<source>XML-based SSO standard. Use this if your application only supports SAML.</source>
</trans-unit>
<trans-unit id="s22f7a7ec3bc2c118">
<source>Legacy applications which don't natively support SSO.</source>
</trans-unit>
<trans-unit id="sc3259eb55cf91e8c">
<source>LDAP</source>
<target>LDAP</target>
@ -1714,103 +1683,9 @@
<trans-unit id="sffd5481034a1bd41">
<source>Provide an LDAP interface for applications and users to authenticate against.</source>
</trans-unit>
<trans-unit id="s3cf89cb47fdde7e9">
<source>Link</source>
</trans-unit>
<trans-unit id="s7ccc013f41f0a2c2">
<source>Authentication method</source>
</trans-unit>
<trans-unit id="sd45fc6453f617152">
<source>LDAP details</source>
</trans-unit>
<trans-unit id="scf72690719b7a8a1">
<source>Create service account</source>
</trans-unit>
<trans-unit id="sbda06adbba493976">
<source>Create provider</source>
<target>Sağlayıcı oluştur</target>
</trans-unit>
<trans-unit id="s12d0cb01d9f6f3b3">
<source>Application Link</source>
</trans-unit>
<trans-unit id="s54a13196915fd76c">
<source>URL which will be opened when a user clicks on the application.</source>
</trans-unit>
<trans-unit id="sa34866db5e891cc6">
<source>Method details</source>
</trans-unit>
<trans-unit id="saf840955862a8cd0">
<source>This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically.</source>
</trans-unit>
<trans-unit id="sb2f3a45baaa52dfd">
<source>By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password.</source>
</trans-unit>
<trans-unit id="se0df976add086ec3">
<source>Web application</source>
</trans-unit>
<trans-unit id="s042fb9b0f9acc21f">
<source>Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP)</source>
</trans-unit>
<trans-unit id="sdf4bd51375026222">
<source>Single-page applications</source>
</trans-unit>
<trans-unit id="s7fa3c3306b77187d">
<source>Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue)</source>
</trans-unit>
<trans-unit id="s3db92162f63a12b4">
<source>Native application</source>
</trans-unit>
<trans-unit id="s2f43274a59b0d384">
<source>Applications which redirect users to a non-web callback (for example, Android, iOS)</source>
</trans-unit>
<trans-unit id="sfa5f0019a09be6a7">
<source>API</source>
</trans-unit>
<trans-unit id="s4d53cad956dfc4e4">
<source>Authentication without user interaction, or machine-to-machine authentication.</source>
</trans-unit>
<trans-unit id="s134083b4613bcf87">
<source>Application type</source>
</trans-unit>
<trans-unit id="s649e7d74f27635f8">
<source>Flow used when users access this application.</source>
</trans-unit>
<trans-unit id="s9c1422a7e3d5c619">
<source>Proxy details</source>
</trans-unit>
<trans-unit id="s7714fdf13df8b682">
<source>External domain</source>
</trans-unit>
<trans-unit id="sf5a280bdcc775c49">
<source>External domain you will be accessing the domain from.</source>
</trans-unit>
<trans-unit id="sb11e750b3de9bb72">
<source>Import SAML Metadata</source>
</trans-unit>
<trans-unit id="s0955bd1d1b1dc710">
<source>Import the metadata document of the applicaation you want to configure.</source>
</trans-unit>
<trans-unit id="s0be509fcae72af45">
<source>Manual configuration</source>
</trans-unit>
<trans-unit id="s485b90ede24fdb19">
<source>Manually configure SAML</source>
</trans-unit>
<trans-unit id="se7d73f906d358a46">
<source>SAML details</source>
</trans-unit>
<trans-unit id="s982a7cdc93aa6fbe">
<source>URL that authentik will redirect back to after successful authentication.</source>
</trans-unit>
<trans-unit id="sc9e75f44b775aa52">
<source>Import SAML metadata</source>
</trans-unit>
<trans-unit id="s0c9670f429e74283">
<source>New application</source>
</trans-unit>
<trans-unit id="s8e1d308c09d1b400">
<source>Create a new application.</source>
</trans-unit>
<trans-unit id="s6ba50bb0842ba1e2">
<source>Applications</source>
<target>Uygulamalar</target>
@ -5834,6 +5709,76 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s24bce955914b1f0a">
<source>Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello).</source>
</trans-unit>
&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD
<trans-unit id="s1cffe58249b04669">
<source>Internal application name used in URLs.</source>
</trans-unit>
<trans-unit id="sb3d4f79d9d8b71e5">
<source>Submit</source>
</trans-unit>
<trans-unit id="se2b29e6cfe59414c">
<source>UI Settings</source>
</trans-unit>
<trans-unit id="sf45734291c57c606">
<source>OAuth2/OpenID</source>
</trans-unit>
<trans-unit id="s836148f721d8913b">
<source>Transparent Reverse Proxy</source>
</trans-unit>
<trans-unit id="s945a6b94361ee45b">
<source>For transparent reverse proxies with required authentication</source>
</trans-unit>
<trans-unit id="s60e79e1793e6bd56">
<source>Forward Auth Single Application</source>
</trans-unit>
<trans-unit id="sadf073913458acbd">
<source>For nginx's auth_request or traefix's forwardAuth</source>
</trans-unit>
<trans-unit id="s4425a1002597151e">
<source>Forward Auth Domain Level</source>
</trans-unit>
<trans-unit id="se770e9498b3bacf6">
<source>For nginx's auth_request or traefix's forwardAuth per root domain</source>
</trans-unit>
<trans-unit id="s40830ec037f34626">
<source>Configure SAML provider manually</source>
</trans-unit>
<trans-unit id="s154eea2a61d44655">
<source>RADIUS Configuration</source>
</trans-unit>
<trans-unit id="sea9fc40dfd1d18b1">
<source>Configure RADIUS provider manually</source>
</trans-unit>
<trans-unit id="s5a8f04c6ef292743">
<source>SCIM configuration</source>
</trans-unit>
<trans-unit id="sa1b0052ae095b9b3">
<source>Configure SCIM provider manually</source>
</trans-unit>
<trans-unit id="s15831fa50a116545">
<source>Saving Application...</source>
</trans-unit>
<trans-unit id="s823abdb61543a826">
<source>Authentik was unable to save this application:</source>
</trans-unit>
<trans-unit id="s848288f8c2265aad">
<source>Your application has been saved</source>
</trans-unit>
<trans-unit id="sf60f1e5b76897c93">
<source>In the Application:</source>
</trans-unit>
<trans-unit id="s7ce65cf482b7bff0">
<source>In the Provider:</source>
</trans-unit>
<trans-unit id="s67d858051b34c38b">
<source>Method's display Name.</source>
</trans-unit>
<trans-unit id="h10ef80d434185070">
<source>Use this provider with nginx's <x id="0" equiv-text="&lt;code&gt;"/>auth_request<x id="1" equiv-text="&lt;/code&gt;"/> or traefik's
<x id="2" equiv-text="&lt;code&gt;"/>forwardAuth<x id="3" equiv-text="&lt;/code&gt;"/>. Each application/domain needs its own provider.
Additionally, on each domain, <x id="4" equiv-text="&lt;code&gt;"/>/outpost.goauthentik.io<x id="5" equiv-text="&lt;/code&gt;"/> must be
routed to the outpost (when using a managed outpost, this is done for you).</source>
</trans-unit>
<trans-unit id="sd18b18f91b804c3f">
<source>Custom attributes</source>
</trans-unit>
@ -5944,6 +5889,9 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s526e2c66bd51ff5f">
<source>Role Info</source>
</trans-unit>
<trans-unit id="s2da4aa7a9abeb653">
<source>Pseudolocale (for testing)</source>
</trans-unit>
</body>
</file>

View file

@ -1837,11 +1837,6 @@
<source>Slug</source>
<target>Slug</target>
</trans-unit>
<trans-unit id="seb8407fa6607c683">
<source>Internal application name, used in URLs.</source>
<target>应用的内部名称,在 URL 中使用。</target>
</trans-unit>
<trans-unit id="sdae55084f6cb2662">
<source>Optionally enter a group name. Applications with identical groups are shown grouped together.</source>
@ -1857,11 +1852,6 @@
<source>Select a provider that this application should use.</source>
<target>选择此应用应该使用的提供程序。</target>
</trans-unit>
<trans-unit id="scd5dff009d1fd847">
<source>Backchannel providers</source>
<target>反向通道提供程序</target>
</trans-unit>
<trans-unit id="s4c6534a118f52fdd">
<source>Select backchannel providers which augment the functionality of the main provider.</source>
@ -2227,11 +2217,6 @@
<source>NameID attribute</source>
<target>NameID 属性</target>
</trans-unit>
<trans-unit id="sf05c693a6e270b79">
<source>SCIM provider is in preview.</source>
<target>SCIM 提供程序处于预览状态。</target>
</trans-unit>
<trans-unit id="s2f0f6691de0b0388">
<source>Warning: Provider is not assigned to an application as backchannel provider.</source>
@ -2252,46 +2237,11 @@
<source>Run sync again</source>
<target>再次运行同步</target>
</trans-unit>
<trans-unit id="sf6a518045ea398f7">
<source>Application details</source>
<target>应用程序详情</target>
</trans-unit>
<trans-unit id="s51b0102a7d31d0bf">
<source>Create application</source>
<target>创建应用程序</target>
</trans-unit>
<trans-unit id="sdae3a34cfb38c10f">
<source>Additional UI settings</source>
<target>其他界面设置</target>
</trans-unit>
<trans-unit id="s233d106d13420dd2">
<source>OAuth2/OIDC</source>
<target>OAuth2/OIDC</target>
</trans-unit>
<trans-unit id="sc2cedfb22488ccb2">
<source>Modern applications, APIs and Single-page applications.</source>
<target>现代应用程序、API 与单页应用程序。</target>
</trans-unit>
<trans-unit id="s1d93562422eeac10">
<source>SAML</source>
<target>SAML</target>
</trans-unit>
<trans-unit id="s5923f8a2226a414c">
<source>XML-based SSO standard. Use this if your application only supports SAML.</source>
<target>基于 XML 的 SSO 标准。如果您的应用程序仅支持 SAML 则应使用。</target>
</trans-unit>
<trans-unit id="s22f7a7ec3bc2c118">
<source>Legacy applications which don't natively support SSO.</source>
<target>不原生支持 SSO 的传统应用程序。</target>
</trans-unit>
<trans-unit id="sc3259eb55cf91e8c">
<source>LDAP</source>
@ -2302,166 +2252,11 @@
<source>Provide an LDAP interface for applications and users to authenticate against.</source>
<target>为应用程序和用户提供 LDAP 接口以进行身份​​验证。</target>
</trans-unit>
<trans-unit id="s3cf89cb47fdde7e9">
<source>Link</source>
<target>链接</target>
</trans-unit>
<trans-unit id="s7ccc013f41f0a2c2">
<source>Authentication method</source>
<target>身份验证方法</target>
</trans-unit>
<trans-unit id="sd45fc6453f617152">
<source>LDAP details</source>
<target>LDAP 详情</target>
</trans-unit>
<trans-unit id="scf72690719b7a8a1">
<source>Create service account</source>
<target>创建服务账户</target>
</trans-unit>
<trans-unit id="sbda06adbba493976">
<source>Create provider</source>
<target>创建提供程序</target>
</trans-unit>
<trans-unit id="s12d0cb01d9f6f3b3">
<source>Application Link</source>
<target>应用程序链接</target>
</trans-unit>
<trans-unit id="s54a13196915fd76c">
<source>URL which will be opened when a user clicks on the application.</source>
<target>用户点击应用程序时将打开的 URL。</target>
</trans-unit>
<trans-unit id="sa34866db5e891cc6">
<source>Method details</source>
<target>方法详情</target>
</trans-unit>
<trans-unit id="saf840955862a8cd0">
<source>This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically.</source>
<target>此配置可用于通过其他 API 或以编程方式处理 authentik 身份验证。</target>
</trans-unit>
<trans-unit id="sb2f3a45baaa52dfd">
<source>By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password.</source>
<target>默认情况下,所有服务账户都可以作为此应用程序进行身份验证,只要它们拥有 app-password 类型的有效令牌。</target>
</trans-unit>
<trans-unit id="se0df976add086ec3">
<source>Web application</source>
<target>Web 应用程序</target>
</trans-unit>
<trans-unit id="s042fb9b0f9acc21f">
<source>Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP)</source>
<target>在服务端处理身份验证的应用程序(例如 Python、Go、Rust、Java、PHP</target>
</trans-unit>
<trans-unit id="sdf4bd51375026222">
<source>Single-page applications</source>
<target>单页应用程序</target>
</trans-unit>
<trans-unit id="s7fa3c3306b77187d">
<source>Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue)</source>
<target>在浏览器内处理身份验证的单页应用程序(例如 Javascript、Angular、React、Vue</target>
</trans-unit>
<trans-unit id="s3db92162f63a12b4">
<source>Native application</source>
<target>原生应用程序</target>
</trans-unit>
<trans-unit id="s2f43274a59b0d384">
<source>Applications which redirect users to a non-web callback (for example, Android, iOS)</source>
<target>重定向用户到非 Web 回调的应用程序(例如 Android、iOS</target>
</trans-unit>
<trans-unit id="sfa5f0019a09be6a7">
<source>API</source>
<target>API</target>
</trans-unit>
<trans-unit id="s4d53cad956dfc4e4">
<source>Authentication without user interaction, or machine-to-machine authentication.</source>
<target>无需用户操作的身份验证,或 M2M机器到机器身份验证。</target>
</trans-unit>
<trans-unit id="s134083b4613bcf87">
<source>Application type</source>
<target>应用程序类型</target>
</trans-unit>
<trans-unit id="s649e7d74f27635f8">
<source>Flow used when users access this application.</source>
<target>用户访问此应用程序时使用的流程。</target>
</trans-unit>
<trans-unit id="s9c1422a7e3d5c619">
<source>Proxy details</source>
<target>代理详情</target>
</trans-unit>
<trans-unit id="s7714fdf13df8b682">
<source>External domain</source>
<target>外部域名</target>
</trans-unit>
<trans-unit id="sf5a280bdcc775c49">
<source>External domain you will be accessing the domain from.</source>
<target>您将从此外部域名访问域名。</target>
</trans-unit>
<trans-unit id="sb11e750b3de9bb72">
<source>Import SAML Metadata</source>
<target>导入 SAML 元数据</target>
</trans-unit>
<trans-unit id="s0955bd1d1b1dc710">
<source>Import the metadata document of the applicaation you want to configure.</source>
<target>导入您要配置的应用程序的元数据文档。</target>
</trans-unit>
<trans-unit id="s0be509fcae72af45">
<source>Manual configuration</source>
<target>手动配置</target>
</trans-unit>
<trans-unit id="s485b90ede24fdb19">
<source>Manually configure SAML</source>
<target>手动配置 SAML</target>
</trans-unit>
<trans-unit id="se7d73f906d358a46">
<source>SAML details</source>
<target>SAML 详情</target>
</trans-unit>
<trans-unit id="s982a7cdc93aa6fbe">
<source>URL that authentik will redirect back to after successful authentication.</source>
<target>身份验证成功后authentik 将重定向回的 URL。</target>
</trans-unit>
<trans-unit id="sc9e75f44b775aa52">
<source>Import SAML metadata</source>
<target>导入 SAML 元数据</target>
</trans-unit>
<trans-unit id="s0c9670f429e74283">
<source>New application</source>
<target>新应用程序</target>
</trans-unit>
<trans-unit id="s8e1d308c09d1b400">
<source>Create a new application.</source>
<target>创建一个新应用程序。</target>
</trans-unit>
<trans-unit id="s6ba50bb0842ba1e2">
<source>Applications</source>
@ -7829,6 +7624,76 @@ Bindings to groups/users are checked against the user of the event.</source>
<source>Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello).</source>
<target>用来配置 WebAuthn 身份验证器(即 Yubikey、FaceID/Windows Hello的阶段。</target>
</trans-unit>
&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD
<trans-unit id="s1cffe58249b04669">
<source>Internal application name used in URLs.</source>
</trans-unit>
<trans-unit id="sb3d4f79d9d8b71e5">
<source>Submit</source>
</trans-unit>
<trans-unit id="se2b29e6cfe59414c">
<source>UI Settings</source>
</trans-unit>
<trans-unit id="sf45734291c57c606">
<source>OAuth2/OpenID</source>
</trans-unit>
<trans-unit id="s836148f721d8913b">
<source>Transparent Reverse Proxy</source>
</trans-unit>
<trans-unit id="s945a6b94361ee45b">
<source>For transparent reverse proxies with required authentication</source>
</trans-unit>
<trans-unit id="s60e79e1793e6bd56">
<source>Forward Auth Single Application</source>
</trans-unit>
<trans-unit id="sadf073913458acbd">
<source>For nginx's auth_request or traefix's forwardAuth</source>
</trans-unit>
<trans-unit id="s4425a1002597151e">
<source>Forward Auth Domain Level</source>
</trans-unit>
<trans-unit id="se770e9498b3bacf6">
<source>For nginx's auth_request or traefix's forwardAuth per root domain</source>
</trans-unit>
<trans-unit id="s40830ec037f34626">
<source>Configure SAML provider manually</source>
</trans-unit>
<trans-unit id="s154eea2a61d44655">
<source>RADIUS Configuration</source>
</trans-unit>
<trans-unit id="sea9fc40dfd1d18b1">
<source>Configure RADIUS provider manually</source>
</trans-unit>
<trans-unit id="s5a8f04c6ef292743">
<source>SCIM configuration</source>
</trans-unit>
<trans-unit id="sa1b0052ae095b9b3">
<source>Configure SCIM provider manually</source>
</trans-unit>
<trans-unit id="s15831fa50a116545">
<source>Saving Application...</source>
</trans-unit>
<trans-unit id="s823abdb61543a826">
<source>Authentik was unable to save this application:</source>
</trans-unit>
<trans-unit id="s848288f8c2265aad">
<source>Your application has been saved</source>
</trans-unit>
<trans-unit id="sf60f1e5b76897c93">
<source>In the Application:</source>
</trans-unit>
<trans-unit id="s7ce65cf482b7bff0">
<source>In the Provider:</source>
</trans-unit>
<trans-unit id="s67d858051b34c38b">
<source>Method's display Name.</source>
</trans-unit>
<trans-unit id="h10ef80d434185070">
<source>Use this provider with nginx's <x id="0" equiv-text="&lt;code&gt;"/>auth_request<x id="1" equiv-text="&lt;/code&gt;"/> or traefik's
<x id="2" equiv-text="&lt;code&gt;"/>forwardAuth<x id="3" equiv-text="&lt;/code&gt;"/>. Each application/domain needs its own provider.
Additionally, on each domain, <x id="4" equiv-text="&lt;code&gt;"/>/outpost.goauthentik.io<x id="5" equiv-text="&lt;/code&gt;"/> must be
routed to the outpost (when using a managed outpost, this is done for you).</source>
</trans-unit>
<trans-unit id="sd18b18f91b804c3f">
<source>Custom attributes</source>
<target>自定义属性</target>
@ -7941,6 +7806,9 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s526e2c66bd51ff5f">
<source>Role Info</source>
</trans-unit>
<trans-unit id="s2da4aa7a9abeb653">
<source>Pseudolocale (for testing)</source>
</trans-unit>
</body>
</file>

View file

@ -1390,10 +1390,6 @@
<source>Slug</source>
<target>Slug</target>
</trans-unit>
<trans-unit id="seb8407fa6607c683">
<source>Internal application name, used in URLs.</source>
<target>应用的内部名称在URL中使用。</target>
</trans-unit>
<trans-unit id="sdae55084f6cb2662">
<source>Optionally enter a group name. Applications with identical groups are shown grouped together.</source>
<target>输入可选的分组名称。分组相同的应用程序会显示在一起。</target>
@ -1405,9 +1401,6 @@
<trans-unit id="s350a616ff5e145ec">
<source>Select a provider that this application should use.</source>
</trans-unit>
<trans-unit id="scd5dff009d1fd847">
<source>Backchannel providers</source>
</trans-unit>
<trans-unit id="s4c6534a118f52fdd">
<source>Select backchannel providers which augment the functionality of the main provider.</source>
</trans-unit>
@ -1681,9 +1674,6 @@
<trans-unit id="sea3bfc143ced73db">
<source>NameID attribute</source>
</trans-unit>
<trans-unit id="sf05c693a6e270b79">
<source>SCIM provider is in preview.</source>
</trans-unit>
<trans-unit id="s2f0f6691de0b0388">
<source>Warning: Provider is not assigned to an application as backchannel provider.</source>
</trans-unit>
@ -1697,31 +1687,9 @@
<source>Run sync again</source>
<target>再次运行同步</target>
</trans-unit>
<trans-unit id="sf6a518045ea398f7">
<source>Application details</source>
</trans-unit>
<trans-unit id="s51b0102a7d31d0bf">
<source>Create application</source>
</trans-unit>
<trans-unit id="sdae3a34cfb38c10f">
<source>Additional UI settings</source>
</trans-unit>
<trans-unit id="s233d106d13420dd2">
<source>OAuth2/OIDC</source>
</trans-unit>
<trans-unit id="sc2cedfb22488ccb2">
<source>Modern applications, APIs and Single-page applications.</source>
</trans-unit>
<trans-unit id="s1d93562422eeac10">
<source>SAML</source>
<target>SAML</target>
</trans-unit>
<trans-unit id="s5923f8a2226a414c">
<source>XML-based SSO standard. Use this if your application only supports SAML.</source>
</trans-unit>
<trans-unit id="s22f7a7ec3bc2c118">
<source>Legacy applications which don't natively support SSO.</source>
</trans-unit>
<trans-unit id="sc3259eb55cf91e8c">
<source>LDAP</source>
<target>LDAP</target>
@ -1729,106 +1697,9 @@
<trans-unit id="sffd5481034a1bd41">
<source>Provide an LDAP interface for applications and users to authenticate against.</source>
</trans-unit>
<trans-unit id="s3cf89cb47fdde7e9">
<source>Link</source>
</trans-unit>
<trans-unit id="s7ccc013f41f0a2c2">
<source>Authentication method</source>
</trans-unit>
<trans-unit id="sd45fc6453f617152">
<source>LDAP details</source>
<target>LDAP 详情</target>
</trans-unit>
<trans-unit id="scf72690719b7a8a1">
<source>Create service account</source>
</trans-unit>
<trans-unit id="sbda06adbba493976">
<source>Create provider</source>
<target>创建提供商</target>
</trans-unit>
<trans-unit id="s12d0cb01d9f6f3b3">
<source>Application Link</source>
</trans-unit>
<trans-unit id="s54a13196915fd76c">
<source>URL which will be opened when a user clicks on the application.</source>
</trans-unit>
<trans-unit id="sa34866db5e891cc6">
<source>Method details</source>
</trans-unit>
<trans-unit id="saf840955862a8cd0">
<source>This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically.</source>
</trans-unit>
<trans-unit id="sb2f3a45baaa52dfd">
<source>By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password.</source>
</trans-unit>
<trans-unit id="se0df976add086ec3">
<source>Web application</source>
</trans-unit>
<trans-unit id="s042fb9b0f9acc21f">
<source>Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP)</source>
</trans-unit>
<trans-unit id="sdf4bd51375026222">
<source>Single-page applications</source>
</trans-unit>
<trans-unit id="s7fa3c3306b77187d">
<source>Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue)</source>
</trans-unit>
<trans-unit id="s3db92162f63a12b4">
<source>Native application</source>
</trans-unit>
<trans-unit id="s2f43274a59b0d384">
<source>Applications which redirect users to a non-web callback (for example, Android, iOS)</source>
</trans-unit>
<trans-unit id="sfa5f0019a09be6a7">
<source>API</source>
</trans-unit>
<trans-unit id="s4d53cad956dfc4e4">
<source>Authentication without user interaction, or machine-to-machine authentication.</source>
</trans-unit>
<trans-unit id="s134083b4613bcf87">
<source>Application type</source>
</trans-unit>
<trans-unit id="s649e7d74f27635f8">
<source>Flow used when users access this application.</source>
</trans-unit>
<trans-unit id="s9c1422a7e3d5c619">
<source>Proxy details</source>
<target>代理详情</target>
</trans-unit>
<trans-unit id="s7714fdf13df8b682">
<source>External domain</source>
</trans-unit>
<trans-unit id="sf5a280bdcc775c49">
<source>External domain you will be accessing the domain from.</source>
</trans-unit>
<trans-unit id="sb11e750b3de9bb72">
<source>Import SAML Metadata</source>
</trans-unit>
<trans-unit id="s0955bd1d1b1dc710">
<source>Import the metadata document of the applicaation you want to configure.</source>
</trans-unit>
<trans-unit id="s0be509fcae72af45">
<source>Manual configuration</source>
</trans-unit>
<trans-unit id="s485b90ede24fdb19">
<source>Manually configure SAML</source>
</trans-unit>
<trans-unit id="se7d73f906d358a46">
<source>SAML details</source>
<target>SAML 详情</target>
</trans-unit>
<trans-unit id="s982a7cdc93aa6fbe">
<source>URL that authentik will redirect back to after successful authentication.</source>
</trans-unit>
<trans-unit id="sc9e75f44b775aa52">
<source>Import SAML metadata</source>
</trans-unit>
<trans-unit id="s0c9670f429e74283">
<source>New application</source>
</trans-unit>
<trans-unit id="s8e1d308c09d1b400">
<source>Create a new application.</source>
</trans-unit>
<trans-unit id="s6ba50bb0842ba1e2">
<source>Applications</source>
<target>应用程序</target>
@ -5886,6 +5757,76 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s24bce955914b1f0a">
<source>Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello).</source>
</trans-unit>
&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD
<trans-unit id="s1cffe58249b04669">
<source>Internal application name used in URLs.</source>
</trans-unit>
<trans-unit id="sb3d4f79d9d8b71e5">
<source>Submit</source>
</trans-unit>
<trans-unit id="se2b29e6cfe59414c">
<source>UI Settings</source>
</trans-unit>
<trans-unit id="sf45734291c57c606">
<source>OAuth2/OpenID</source>
</trans-unit>
<trans-unit id="s836148f721d8913b">
<source>Transparent Reverse Proxy</source>
</trans-unit>
<trans-unit id="s945a6b94361ee45b">
<source>For transparent reverse proxies with required authentication</source>
</trans-unit>
<trans-unit id="s60e79e1793e6bd56">
<source>Forward Auth Single Application</source>
</trans-unit>
<trans-unit id="sadf073913458acbd">
<source>For nginx's auth_request or traefix's forwardAuth</source>
</trans-unit>
<trans-unit id="s4425a1002597151e">
<source>Forward Auth Domain Level</source>
</trans-unit>
<trans-unit id="se770e9498b3bacf6">
<source>For nginx's auth_request or traefix's forwardAuth per root domain</source>
</trans-unit>
<trans-unit id="s40830ec037f34626">
<source>Configure SAML provider manually</source>
</trans-unit>
<trans-unit id="s154eea2a61d44655">
<source>RADIUS Configuration</source>
</trans-unit>
<trans-unit id="sea9fc40dfd1d18b1">
<source>Configure RADIUS provider manually</source>
</trans-unit>
<trans-unit id="s5a8f04c6ef292743">
<source>SCIM configuration</source>
</trans-unit>
<trans-unit id="sa1b0052ae095b9b3">
<source>Configure SCIM provider manually</source>
</trans-unit>
<trans-unit id="s15831fa50a116545">
<source>Saving Application...</source>
</trans-unit>
<trans-unit id="s823abdb61543a826">
<source>Authentik was unable to save this application:</source>
</trans-unit>
<trans-unit id="s848288f8c2265aad">
<source>Your application has been saved</source>
</trans-unit>
<trans-unit id="sf60f1e5b76897c93">
<source>In the Application:</source>
</trans-unit>
<trans-unit id="s7ce65cf482b7bff0">
<source>In the Provider:</source>
</trans-unit>
<trans-unit id="s67d858051b34c38b">
<source>Method's display Name.</source>
</trans-unit>
<trans-unit id="h10ef80d434185070">
<source>Use this provider with nginx's <x id="0" equiv-text="&lt;code&gt;"/>auth_request<x id="1" equiv-text="&lt;/code&gt;"/> or traefik's
<x id="2" equiv-text="&lt;code&gt;"/>forwardAuth<x id="3" equiv-text="&lt;/code&gt;"/>. Each application/domain needs its own provider.
Additionally, on each domain, <x id="4" equiv-text="&lt;code&gt;"/>/outpost.goauthentik.io<x id="5" equiv-text="&lt;/code&gt;"/> must be
routed to the outpost (when using a managed outpost, this is done for you).</source>
</trans-unit>
<trans-unit id="sd18b18f91b804c3f">
<source>Custom attributes</source>
</trans-unit>
@ -5996,6 +5937,9 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s526e2c66bd51ff5f">
<source>Role Info</source>
</trans-unit>
<trans-unit id="s2da4aa7a9abeb653">
<source>Pseudolocale (for testing)</source>
</trans-unit>
</body>
</file>

View file

@ -1390,10 +1390,6 @@
<source>Slug</source>
<target>Slug</target>
</trans-unit>
<trans-unit id="seb8407fa6607c683">
<source>Internal application name, used in URLs.</source>
<target>应用的内部名称在URL中使用。</target>
</trans-unit>
<trans-unit id="sdae55084f6cb2662">
<source>Optionally enter a group name. Applications with identical groups are shown grouped together.</source>
<target>输入可选的分组名称。分组相同的应用程序会显示在一起。</target>
@ -1405,9 +1401,6 @@
<trans-unit id="s350a616ff5e145ec">
<source>Select a provider that this application should use.</source>
</trans-unit>
<trans-unit id="scd5dff009d1fd847">
<source>Backchannel providers</source>
</trans-unit>
<trans-unit id="s4c6534a118f52fdd">
<source>Select backchannel providers which augment the functionality of the main provider.</source>
</trans-unit>
@ -1681,9 +1674,6 @@
<trans-unit id="sea3bfc143ced73db">
<source>NameID attribute</source>
</trans-unit>
<trans-unit id="sf05c693a6e270b79">
<source>SCIM provider is in preview.</source>
</trans-unit>
<trans-unit id="s2f0f6691de0b0388">
<source>Warning: Provider is not assigned to an application as backchannel provider.</source>
</trans-unit>
@ -1697,31 +1687,9 @@
<source>Run sync again</source>
<target>再次运行同步</target>
</trans-unit>
<trans-unit id="sf6a518045ea398f7">
<source>Application details</source>
</trans-unit>
<trans-unit id="s51b0102a7d31d0bf">
<source>Create application</source>
</trans-unit>
<trans-unit id="sdae3a34cfb38c10f">
<source>Additional UI settings</source>
</trans-unit>
<trans-unit id="s233d106d13420dd2">
<source>OAuth2/OIDC</source>
</trans-unit>
<trans-unit id="sc2cedfb22488ccb2">
<source>Modern applications, APIs and Single-page applications.</source>
</trans-unit>
<trans-unit id="s1d93562422eeac10">
<source>SAML</source>
<target>SAML</target>
</trans-unit>
<trans-unit id="s5923f8a2226a414c">
<source>XML-based SSO standard. Use this if your application only supports SAML.</source>
</trans-unit>
<trans-unit id="s22f7a7ec3bc2c118">
<source>Legacy applications which don't natively support SSO.</source>
</trans-unit>
<trans-unit id="sc3259eb55cf91e8c">
<source>LDAP</source>
<target>LDAP</target>
@ -1729,106 +1697,9 @@
<trans-unit id="sffd5481034a1bd41">
<source>Provide an LDAP interface for applications and users to authenticate against.</source>
</trans-unit>
<trans-unit id="s3cf89cb47fdde7e9">
<source>Link</source>
</trans-unit>
<trans-unit id="s7ccc013f41f0a2c2">
<source>Authentication method</source>
</trans-unit>
<trans-unit id="sd45fc6453f617152">
<source>LDAP details</source>
<target>LDAP 详情</target>
</trans-unit>
<trans-unit id="scf72690719b7a8a1">
<source>Create service account</source>
</trans-unit>
<trans-unit id="sbda06adbba493976">
<source>Create provider</source>
<target>创建提供商</target>
</trans-unit>
<trans-unit id="s12d0cb01d9f6f3b3">
<source>Application Link</source>
</trans-unit>
<trans-unit id="s54a13196915fd76c">
<source>URL which will be opened when a user clicks on the application.</source>
</trans-unit>
<trans-unit id="sa34866db5e891cc6">
<source>Method details</source>
</trans-unit>
<trans-unit id="saf840955862a8cd0">
<source>This configuration can be used to authenticate to authentik with other APIs other otherwise programmatically.</source>
</trans-unit>
<trans-unit id="sb2f3a45baaa52dfd">
<source>By default, all service accounts can authenticate as this application, as long as they have a valid token of the type app-password.</source>
</trans-unit>
<trans-unit id="se0df976add086ec3">
<source>Web application</source>
</trans-unit>
<trans-unit id="s042fb9b0f9acc21f">
<source>Applications which handle the authentication server-side (for example, Python, Go, Rust, Java, PHP)</source>
</trans-unit>
<trans-unit id="sdf4bd51375026222">
<source>Single-page applications</source>
</trans-unit>
<trans-unit id="s7fa3c3306b77187d">
<source>Single-page applications which handle authentication in the browser (for example, Javascript, Angular, React, Vue)</source>
</trans-unit>
<trans-unit id="s3db92162f63a12b4">
<source>Native application</source>
</trans-unit>
<trans-unit id="s2f43274a59b0d384">
<source>Applications which redirect users to a non-web callback (for example, Android, iOS)</source>
</trans-unit>
<trans-unit id="sfa5f0019a09be6a7">
<source>API</source>
</trans-unit>
<trans-unit id="s4d53cad956dfc4e4">
<source>Authentication without user interaction, or machine-to-machine authentication.</source>
</trans-unit>
<trans-unit id="s134083b4613bcf87">
<source>Application type</source>
</trans-unit>
<trans-unit id="s649e7d74f27635f8">
<source>Flow used when users access this application.</source>
</trans-unit>
<trans-unit id="s9c1422a7e3d5c619">
<source>Proxy details</source>
<target>代理详情</target>
</trans-unit>
<trans-unit id="s7714fdf13df8b682">
<source>External domain</source>
</trans-unit>
<trans-unit id="sf5a280bdcc775c49">
<source>External domain you will be accessing the domain from.</source>
</trans-unit>
<trans-unit id="sb11e750b3de9bb72">
<source>Import SAML Metadata</source>
</trans-unit>
<trans-unit id="s0955bd1d1b1dc710">
<source>Import the metadata document of the applicaation you want to configure.</source>
</trans-unit>
<trans-unit id="s0be509fcae72af45">
<source>Manual configuration</source>
</trans-unit>
<trans-unit id="s485b90ede24fdb19">
<source>Manually configure SAML</source>
</trans-unit>
<trans-unit id="se7d73f906d358a46">
<source>SAML details</source>
<target>SAML 详情</target>
</trans-unit>
<trans-unit id="s982a7cdc93aa6fbe">
<source>URL that authentik will redirect back to after successful authentication.</source>
</trans-unit>
<trans-unit id="sc9e75f44b775aa52">
<source>Import SAML metadata</source>
</trans-unit>
<trans-unit id="s0c9670f429e74283">
<source>New application</source>
</trans-unit>
<trans-unit id="s8e1d308c09d1b400">
<source>Create a new application.</source>
</trans-unit>
<trans-unit id="s6ba50bb0842ba1e2">
<source>Applications</source>
<target>应用程序</target>
@ -5885,6 +5756,76 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s24bce955914b1f0a">
<source>Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello).</source>
</trans-unit>
&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD
<trans-unit id="s1cffe58249b04669">
<source>Internal application name used in URLs.</source>
</trans-unit>
<trans-unit id="sb3d4f79d9d8b71e5">
<source>Submit</source>
</trans-unit>
<trans-unit id="se2b29e6cfe59414c">
<source>UI Settings</source>
</trans-unit>
<trans-unit id="sf45734291c57c606">
<source>OAuth2/OpenID</source>
</trans-unit>
<trans-unit id="s836148f721d8913b">
<source>Transparent Reverse Proxy</source>
</trans-unit>
<trans-unit id="s945a6b94361ee45b">
<source>For transparent reverse proxies with required authentication</source>
</trans-unit>
<trans-unit id="s60e79e1793e6bd56">
<source>Forward Auth Single Application</source>
</trans-unit>
<trans-unit id="sadf073913458acbd">
<source>For nginx's auth_request or traefix's forwardAuth</source>
</trans-unit>
<trans-unit id="s4425a1002597151e">
<source>Forward Auth Domain Level</source>
</trans-unit>
<trans-unit id="se770e9498b3bacf6">
<source>For nginx's auth_request or traefix's forwardAuth per root domain</source>
</trans-unit>
<trans-unit id="s40830ec037f34626">
<source>Configure SAML provider manually</source>
</trans-unit>
<trans-unit id="s154eea2a61d44655">
<source>RADIUS Configuration</source>
</trans-unit>
<trans-unit id="sea9fc40dfd1d18b1">
<source>Configure RADIUS provider manually</source>
</trans-unit>
<trans-unit id="s5a8f04c6ef292743">
<source>SCIM configuration</source>
</trans-unit>
<trans-unit id="sa1b0052ae095b9b3">
<source>Configure SCIM provider manually</source>
</trans-unit>
<trans-unit id="s15831fa50a116545">
<source>Saving Application...</source>
</trans-unit>
<trans-unit id="s823abdb61543a826">
<source>Authentik was unable to save this application:</source>
</trans-unit>
<trans-unit id="s848288f8c2265aad">
<source>Your application has been saved</source>
</trans-unit>
<trans-unit id="sf60f1e5b76897c93">
<source>In the Application:</source>
</trans-unit>
<trans-unit id="s7ce65cf482b7bff0">
<source>In the Provider:</source>
</trans-unit>
<trans-unit id="s67d858051b34c38b">
<source>Method's display Name.</source>
</trans-unit>
<trans-unit id="h10ef80d434185070">
<source>Use this provider with nginx's <x id="0" equiv-text="&lt;code&gt;"/>auth_request<x id="1" equiv-text="&lt;/code&gt;"/> or traefik's
<x id="2" equiv-text="&lt;code&gt;"/>forwardAuth<x id="3" equiv-text="&lt;/code&gt;"/>. Each application/domain needs its own provider.
Additionally, on each domain, <x id="4" equiv-text="&lt;code&gt;"/>/outpost.goauthentik.io<x id="5" equiv-text="&lt;/code&gt;"/> must be
routed to the outpost (when using a managed outpost, this is done for you).</source>
</trans-unit>
<trans-unit id="sd18b18f91b804c3f">
<source>Custom attributes</source>
</trans-unit>
@ -5995,6 +5936,9 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s526e2c66bd51ff5f">
<source>Role Info</source>
</trans-unit>
<trans-unit id="s2da4aa7a9abeb653">
<source>Pseudolocale (for testing)</source>
</trans-unit>
</body>
</file>