Repository: jkroepke/openvpn-auth-oauth2
Pull request #392 (2025-01-25 23:17:54)

Add super-lint
#### What this PR does / why we need it

#### Which issue this PR fixes

*(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes #

- fixes #

#### Special notes for your reviewer

#### Particularly user-facing changes

#### Checklist

Complete these before marking the PR as `ready to review`:

<!-- [Place an '[x]' (no spaces) in all applicable fields.] -->

- [ ] [DCO](https://github.com/prometheus-community/helm-charts/blob/main/CONTRIBUTING.md#sign-off-your-work) signed
- [ ] The PR title has a summary of the changes
- [ ] The PR body has a summary to reflect any significant (and particularly user-facing) changes introduced by this PR


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/jkroepke/openvpn-auth-oauth2/pull/392?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jan-Otto+Kr%C3%B6pke) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 83.19%. Comparing base [(`3c2c841`)](https://app.codecov.io/gh/jkroepke/openvpn-auth-oauth2/commit/3c2c84197d2c4292489bc82e9fa0bea719810c72?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jan-Otto+Kr%C3%B6pke) to head [(`af4d4fb`)](https://app.codecov.io/gh/jkroepke/openvpn-auth-oauth2/commit/af4d4fb53d80baf6204f10107a006a4394b75031?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jan-Otto+Kr%C3%B6pke).

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #392      +/-   ##
==========================================
- Coverage   83.43%   83.19%   -0.25%     
==========================================
  Files          50       50              
  Lines        2856     2856              
==========================================
- Hits         2383     2376       -7     
- Misses        361      366       +5     
- Partials      112      114       +2     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/jkroepke/openvpn-auth-oauth2/pull/392?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jan-Otto+Kr%C3%B6pke).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jan-Otto+Kr%C3%B6pke).


Patch:
diff --git a/.codecov.yml b/.codecov.yml
index ddcd940..71f1879 100644
--- a/.codecov.yml
+++ b/.codecov.yml
@@ -1,3 +1,4 @@
+---
 coverage:
   status:
     project: false
diff --git a/.editorconfig b/.editorconfig
index e0c8846..f03e60d 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -6,9 +6,13 @@ insert_final_newline = true
 charset = utf-8
 trim_trailing_whitespace = true
 
-[*.go]
-max_line_length = 160
+[{Makefile,*.go,go.mod,go.sum}]
 indent_style = tab
+indent_size = tab
+max_line_length = 160
+
+[*_test.go]
+max_line_length = off
 
 [*.{yml,yaml}]
 indent_size = 2
@@ -17,4 +21,7 @@ indent_size = 2
 indent_size = 2
 
 [*.md]
-indent_size = 4
\ No newline at end of file
+indent_size = 2
+
+[.run/*.xml]
+insert_final_newline = false
diff --git a/.editorconfig-checker.json b/.editorconfig-checker.json
new file mode 100644
index 0000000..061d5c7
--- /dev/null
+++ b/.editorconfig-checker.json
@@ -0,0 +1,3 @@
+{
+  "Exclude": ["*.md$"]
+}
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 95dba0c..d38fe21 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -2,10 +2,6 @@ name: CI
 
 env:
   PACKAGES: openvpn crossbuild-essential-arm64 libssl-dev:arm64
-  # renovate: github=golangci/golangci-lint
-  GO_LINT_CI_VERSION: v1.63.4
-  # renovate: github=goreleaser/goreleaser
-  GORELEASER_VERSION: v2.5.1
 
 on:
   workflow_dispatch:
@@ -18,7 +14,7 @@ on:
 
 jobs:
   build:
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-24.04
     name: Build & Test
     steps:
       - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
@@ -29,7 +25,6 @@ jobs:
         with:
           go-version-file: 'go.mod'
 
-      - run: go mod tidy -diff
       - run: go build
       - run: go test ./... -timeout 20s -race -covermode=atomic -coverprofile=coverage.out -coverpkg=./...
       - run: go test ./... -timeout 20s -bench . -benchmem -count 3
@@ -44,7 +39,8 @@ jobs:
       - name: go build (with goreleaser)
         uses: goreleaser/goreleaser-action@9ed2f89a662bf1735a48bc8557fd212fa902bebf # v6.1.0
         with:
-          version: ${{ env.GORELEASER_VERSION }}
+          # renovate: github=goreleaser/goreleaser
+          version: v2.5.1
           args: release --snapshot
         env:
           GITHUB_TOKEN: ""
@@ -59,31 +55,69 @@ jobs:
           path: dist/
   lint:
     name: lint
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-24.04
     steps:
       - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
       - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
         with:
           go-version-file: 'go.mod'
 
+      - run: go mod tidy -diff
+
       - name: golangci-lint
         uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1
         with:
           version: ${{ env.GO_LINT_CI_VERSION }}
 
+  super-lint:
+    name: super-lint
+    runs-on: ubuntu-24.04
+    permissions:
+      contents: read
+    steps:
+      - name: Checkout Code
+        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        with:
+          fetch-depth: 0
+
+      - name: Lint Code Base
+        uses: super-linter/super-linter/slim@85f7611e0f7b53c8573cca84aa0ed4344f6f6a4d # v7.2.1
+        env:
+          EDITORCONFIG_FILE_NAME: .editorconfig-checker.json
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          MULTI_STATUS: false
+          LINTER_RULES_PATH: .
+          VALIDATE_ALL_CODEBASE: true
+          VALIDATE_BASH: true
+          VALIDATE_BASH_EXEC: true
+          VALIDATE_EDITORCONFIG: true
+          VALIDATE_ENV: true
+          VALIDATE_GO_RELEASER: true
+          VALIDATE_GITHUB_ACTIONS: true
+          VALIDATE_HTML: true
+          VALIDATE_JSON: true
+          VALIDATE_NATURAL_LANGUAGE: true
+          # VALIDATE_MARKDOWN: false
+          VALIDATE_RENOVATE: true
+          VALIDATE_SHELL_SHFMT: true
+          VALIDATE_XML: true
+          VALIDATE_YAML: true
+
   publish:
     name: Publish package
     if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
     needs:
       - build
       - lint
-    runs-on: ubuntu-latest
+      - super-lint
+    runs-on: ubuntu-24.04
     permissions:
       contents: write
     steps:
       - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
         with:
           fetch-depth: 0
+
       - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
         with:
           go-version-file: 'go.mod'
@@ -97,7 +131,8 @@ jobs:
       - name: Run GoReleaser
         uses: goreleaser/goreleaser-action@9ed2f89a662bf1735a48bc8557fd212fa902bebf # v6.1.0
         with:
-          version: ${{ env.GORELEASER_VERSION }}
+          # renovate: github=goreleaser/goreleaser
+          version: v2.5.1
           args: release --clean
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/spelling.yaml b/.github/workflows/spelling.yaml
deleted file mode 100644
index 3162716..0000000
--- a/.github/workflows/spelling.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
-name: Spell checking
-
-# Trigger on pull requests, and pushes to master branch.
-on:
-  push:
-    branches:
-      - main
-  pull_request:
-    branches:
-      - main
-
-jobs:
-  codespell:
-    name: Check for spelling errors
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
-      - uses: codespell-project/actions-codespell@406322ec52dd7b488e48c1c4b82e2a8b3a1bf630
-        with:
-          check_filenames: true
-          # When using this Action in other repos, the --skip option below can be removed
-          skip: ./.git,./CODE_OF_CONDUCT.md,go.mod,go.sum,go.work,go.work.sum,./internal/ui/assets/i18n
diff --git a/Makefile b/Makefile
index aafc30e..08e9c31 100644
--- a/Makefile
+++ b/Makefile
@@ -18,9 +18,9 @@ GOOS := $(shell go env GOOS)
 
 # Determine the output file extension based on the GOOS value
 ifeq ($(GOOS),windows)
-    EXT := .exe
+	EXT := .exe
 else
-    EXT :=
+	EXT :=
 endif
 
 ##
diff --git a/README.md b/README.md
index 449d56d..52c719e 100644
--- a/README.md
+++ b/README.md
@@ -60,12 +60,20 @@ Thanks to JetBrains IDEs and Sparklabs for their support.
   </thead>
   <tbody>
     <tr>
-      <td><center><a href="https://www.jetbrains.com/?from=jkroepke"><img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg" alt="JetBrains-Logo (Haupt) logo" height="200px"></a></center></td>
-      <td><center><a href="https://www.sparklabs.com/viscosity"><img src="https://www.sparklabs.com/static/other/logo_assets/logo_cropped.png" alt="Sparklabs Viscosity logo" height="200px"></a></center></td>
+      <td>
+        <center>
+          <a href="https://www.jetbrains.com/?from=jkroepke">
+            <img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg" alt="JetBrains-Logo (Haupt) logo" height="200px">
+          </a>
+        </center>
+      </td>
+      <td>
+        <center>
+          <a href="https://www.sparklabs.com/viscosity">
+            <img src="https://www.sparklabs.com/static/other/logo_assets/logo_cropped.png" alt="Sparklabs Viscosity logo" height="200px">
+          </a>
+        </center>
+      </td>
     </tr>
   </tbody>
 </table>
-
-
-
-
diff --git a/docs/Article - How OIDC SSO Authentication works with OpenVPN Community Server.md b/docs/Article - How OIDC SSO Authentication works with OpenVPN Community Server.md
index 4b208aa..896333f 100644
--- a/docs/Article - How OIDC SSO Authentication works with OpenVPN Community Server.md
+++ b/docs/Article - How OIDC SSO Authentication works with OpenVPN Community Server.md
@@ -1,6 +1,6 @@
 # Introduction
 
-[OpenID Connect (OIDC)](https://auth0.com/intro-to-iam/what-is-openid-connect-oidc) is a powerful identity layer built on top of the OAuth 2.0 protocol. It enables clients to verify the identity of an end-user based on the authentication performed by an authorization server. Single Sign-On (SSO) is a user authentication service that allows a user to use one set of login credentials to access multiple applications.
+[OpenID Connect (OIDC)](https://auth0.com/intro-to-iam/what-is-openid-connect-oidc) is a powerful identity layer built on top of the OAuth 2.0 protocol. It enables clients to verify the identity of an end user based on the authentication performed by an authorization server. Single Sign-On (SSO) is a user authentication service that allows a user to use one set of login credentials to access multiple applications.
 
 In the realm of secure and seamless user authentication, the [`openvpn-auth-oauth2`](https://github.com/jkroepke/openvpn-auth-oauth2) plugin emerges as a game-changer. It integrates OpenVPN Community Server with any OIDC provider, leveraging the robustness of OIDC and the convenience of SSO. This powerful combination not only simplifies the authentication process but also significantly enhances the security of your applications.
 
@@ -90,4 +90,4 @@ The `openvpn-auth-oauth2` plugin offers several benefits when used for authentic
 
 5. **Scalability**: By delegating authentication to an OIDC provider, the plugin allows the OpenVPN Community Server to scale more effectively. The server can focus on its core functionality, while the OIDC provider handles the potentially resource-intensive process of user authentication.
 
-6. **Security**: Using the `openvpn-auth-oauth2` plugin for authentication can improve the security of your application. OIDC providers often have robust security measures in place, including multi-factor authentication, anomaly detection, and secure handling of user credentials. By integrating with an OIDC provider, you can leverage these security measures for your application.
\ No newline at end of file
+6. **Security**: Using the `openvpn-auth-oauth2` plugin for authentication can improve the security of your application. OIDC providers often have robust security measures in place, including multi-factor authentication, anomaly detection, and secure handling of user credentials. By integrating with an OIDC provider, you can leverage these security measures for your application.
diff --git a/docs/Debugging Errors.md b/docs/Debugging Errors.md
index 6df25c5..1ca145f 100644
--- a/docs/Debugging Errors.md
+++ b/docs/Debugging Errors.md
@@ -25,15 +25,15 @@ I assume you are using a Chrome-based Browser.
 4. Continue the auth flow.
 5. On the access-denied screen, check the Browser Console for any errors.
 6. Then get the Network tab again.
-   There is a request with `/oauth2/start` or just `start`.
-   Click on Cookies and check if the response cookie is present.
-   Example Screenshot:
-   ![](./img/debugging-error-cookie-start.png)
+    There is a request with `/oauth2/start` or just `start`.
+    Click on Cookies and check if the response cookie is present.
+    Example Screenshot:
+    ![](./img/debugging-error-cookie-start.png)
 7. Then get the Network tab again.
-   There is a request with `/oauth2/callback` or just `callback`.
-   Click on Cookies and check if the request cookie is present.
-   Example Screenshot:
-   ![](./img/debugging-error-cookie-callback.png)
+    There is a request with `/oauth2/callback` or just `callback`.
+    Click on Cookies and check if the request cookie is present.
+    Example Screenshot:
+    ![](./img/debugging-error-cookie-callback.png)
 
 ## Error Message `Received control message: 'PUSH_REQUEST'` in OpenVPN Client v3
 
@@ -61,7 +61,7 @@ Sometimes the client stuck at this stage `Received control message: 'PUSH_REQUES
  PACKETS_OUT : 20
 ```
 
-If you see this message on the Client side (version3),
+If you see this message on the client-side (version3),
 you may want to make sure you add these lines to your oauth configuration file
 
 <table>
diff --git a/docs/Demo.md b/docs/Demo.md
index bbc0167..dab89aa 100644
--- a/docs/Demo.md
+++ b/docs/Demo.md
@@ -25,14 +25,14 @@ To run the demo, follow these steps:
 3. Navigate to the directory where the `docker-compose.yaml` file is located.
 4. Run the following command to start the Docker Compose environment:
 
-   ```bash
-   docker compose up
-   ```
-   This command will start all the necessary services. It may take a few minutes for all services to start and initialize.
+    ```bash
+    docker compose up
+    ```
+    This command will start all the necessary services. It may take a few minutes for all services to start and initialize.
 
 5. After all services have started, a client configuration for OpenVPN will be created in the `config/client/` directory.
-   This configuration can be imported into any OpenVPN client.
-   Viscosity may not work here, because the client block local network access.
+    This configuration can be imported into any OpenVPN client.
+    Viscosity may not work here, because the client block local network access.
 6. Import the client configuration into your OpenVPN client.
 7. Connect to the OpenVPN server using the demo user credentials (username: `demo`, password: `demo123`).
 
diff --git a/docs/FAQ.md b/docs/FAQ.md
index 76e0ef5..9027106 100644
--- a/docs/FAQ.md
+++ b/docs/FAQ.md
@@ -47,19 +47,19 @@ A: Although openvpn-auth-oauth2 theoretically doesn't require client-side authen
 **Potential Workarounds:**
 
 1. **Configure Client Certificates**
-   Implement client certificates to enable client-side authentication.
+    Implement client certificates to enable client-side authentication.
 
 2. **Use Inline auth-user-pass**
-   OpenVPN accepts `auth-user-pass` for client-side authentication. You can define the username and password inline to prevent the OpenVPN GUI from requesting a password.
+    OpenVPN accepts `auth-user-pass` for client-side authentication. You can define the username and password inline to prevent the OpenVPN GUI from requesting a password.
 
-   ```
-   <auth-user-pass>
-   username
-   password
-   </auth-user-pass>
-   ```
+    ```
+    <auth-user-pass>
+    username
+    password
+    </auth-user-pass>
+    ```
 
-   Note: The username/password can be any dummy value as they won't be validated by openvpn-auth-oauth2 or OpenVPN itself.
+    Note: The username/password can be any dummy value as they won't be validated by openvpn-auth-oauth2 or OpenVPN itself.
 
 ## Q: `Provider did not return a id_token. Validation of user data is not possible.` is logged, but my provider is returning an id_token.
 
@@ -73,7 +73,7 @@ A: The openvpn-auth-oauth2 plugin doesn’t log out the user from the OIDC serve
 after the VPN session ends because the OpenID Connect (OIDC) protocol’s end session endpoint,
 while available, isn’t suitable in this context.
 The end session endpoint generates a URL
-intended for the end-user to manually initiate the logout process via their browser.
+intended for the end user to manually initiate the logout process via their browser.
 Since OpenVPN operates without direct interaction with the user's browser upon logout,
 there's no mechanism to automatically open the URL for the user.
 
diff --git a/docs/Installation.md b/docs/Installation.md
index f6c09d6..c0d9f30 100644
--- a/docs/Installation.md
+++ b/docs/Installation.md
@@ -41,11 +41,11 @@ If you prefer to build the binary yourself, follow these steps:
 3. Open a terminal.
 4. Navigate to the directory where you downloaded the source code.
 5. Build the binary using the following command:
-   ```bash
-   make build
-   ```
-   This will create a binary file named openvpn-auth-oauth2.
+    ```bash
+    make build
+    ```
+    This will create a binary file named openvpn-auth-oauth2.
 6. Move the `openvpn-auth-oauth2` binary to /usr/bin/ using the following command:
-   ```bash
-   sudo mv openvpn-auth-oauth2 /usr/bin/
-   ```
+    ```bash
+    sudo mv openvpn-auth-oauth2 /usr/bin/
+    ```
diff --git a/docs/Layout Customization.md b/docs/Layout Customization.md
index 95f3333..f1bb221 100644
--- a/docs/Layout Customization.md
+++ b/docs/Layout Customization.md
@@ -22,7 +22,7 @@ To override the default assets, you can configure `http.assets-path` with the pa
 The default assets are here:
 
 - `style.css`: CSS file to enrich the default layout. By default, it is empty.
-- `mvp.css`: [MVP](https://github.com/andybrewer/mvp) css framework
+- `mvp.css`: [MVP](https://github.com/andybrewer/mvp) CSS framework
 - `favicon.png`: Favicon of the login page
 - `i18n.js`: Localization script
 - `i18n/<lang>.json`: Language specific localization file. <lang> is the language code, e.g., `en` for English.
@@ -31,7 +31,7 @@ The default assets are here:
 ## Custom localization
 
 If you want to provide custom localization, you have to configure `http.assets-path` first. In the assets directory,
-create a new directory named `i18n` and put your localization files in there. The file name must be the language code
+create a new directory named `i18n` and put your localization files in there. The filename must be the language code
 followed by `.json`. For example, `en.json` for English.
 
 Instead, providing a custom localization file locally, think about to submit a pull request to the project to provide
diff --git a/docs/Non-interactive session refresh.md b/docs/Non-interactive session refresh.md
index 81438bc..31f7331 100644
--- a/docs/Non-interactive session refresh.md
+++ b/docs/Non-interactive session refresh.md
@@ -7,7 +7,7 @@ This means users must log in interactively each time they authenticate, includin
 However, you can change this behavior by enabling the `oauth2.refresh.enabled=true` setting.
 This allows `openvpn-auth-oauth2` to store either the connection ID or SessionID (`oauth2.refresh.use-session-id=true`),
 accepting connections without additional login checks. SessionIDs are available in OpenVPN, if
-`auth-gen-token [lifetime] external-auth` is configured on server side.
+`auth-gen-token [lifetime] external-auth` is configured on server-side.
 
 When `oauth2.refresh.validate-user=true` is set, `openvpn-auth-oauth2`
 requests a [refresh token](https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/)
diff --git a/docs/Providers.md b/docs/Providers.md
index cf944fa..9431ce7 100644
--- a/docs/Providers.md
+++ b/docs/Providers.md
@@ -88,17 +88,17 @@ How require multiple groups, check you could define `CONFIG_OAUTH2_VALIDATE_GROU
 1. Login as admin into your [Google console](https://console.cloud.google.com/).
 2. In the project Dashboard center pane, choose **"APIs & Services"**.
 3. If necessary, complete the `OAuth consent screen` wizard. You will probably want to create an `Internal` application.
-   - If you reuse an existing application, your users may already have given consent for the usage of this application,
-     which may not include refresh tokens.
+    * If you reuse an existing application, your users may already have given consent for the usage of this application,
+      which may not include refresh tokens.
 4. In the left Nav pane, choose **"Credentials"**.
 5. In the center pane, choose **"OAuth consent screen"** tab. Fill in **"Product name shown to users"** and hit save.
 6. In the center pane, choose **"Credentials"** tab.
-   * Open the "New credentials"** drop down
-   * Choose **"OAuth client ID"**
-   * Choose **"Web application"**
-   * Application name is freeform, choose something appropriate
-   * Authorized redirect URIs is the location of oauth2/callback ex: https://yourdomain:9000/oauth2/callback
-   * Choose "Create"
+    * Open the "New credentials"** drop down
+    * Choose **"OAuth client ID"**
+    * Choose **"Web application"**
+    * Application name is freeform, choose something appropriate
+    * Authorized redirect URIs is the location of oauth2/callback ex: https://yourdomain:9000/oauth2/callback
+    * Choose "Create"
 7. Take note of the Client ID and Client Secret.
 
 ### Restrict auth to specific Google Groups in your domain. (optional)
diff --git a/docs/README.md b/docs/README.md
index 280a7ca..0ab9525 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,5 +1,5 @@
 # Documentation of openvpn-auth-oauth2
 
-Please note, all markdown files are synced with the openvpn-auth-oauth2 wiki to improve the readability of the pages.
+Please note, all Markdown files are synced with the openvpn-auth-oauth2 wiki to improve the readability of the pages.
 
 https://github.com/jkroepke/openvpn-auth-oauth2/wiki
diff --git a/internal/ui/assets/i18n/nl.json b/internal/ui/assets/i18n/nl.json
index e924d89..91d1283 100644
--- a/internal/ui/assets/i18n/nl.json
+++ b/internal/ui/assets/i18n/nl.json
@@ -2,6 +2,6 @@
   "Access denied": "Toegang geweigerd",
   "Access granted": "Toegang verleend",
   "Error ID": "Fout-ID",
-  "Please contact your administrator.": "Neem contact op met uw beheerder."
+  "Please contact your administrator.": "Neem contact op met uw beheerder.",
   "You can close this window now.": "U kunt dit venster nu sluiten."
 }
diff --git a/internal/ui/assets/i18n/ua.json b/internal/ui/assets/i18n/ua.json
index 1bf7442..0f4bc6c 100644
--- a/internal/ui/assets/i18n/ua.json
+++ b/internal/ui/assets/i18n/ua.json
@@ -2,6 +2,6 @@
   "Access denied": "Доступ заборонено",
   "Access granted": "Доступ дозволено",
   "Error ID": "Код помилки",
-  "Please contact your administrator.": "Будь ласка, зв'яжіться зі своїм адміністратором."
+  "Please contact your administrator.": "Будь ласка, зв'яжіться зі своїм адміністратором.",
   "You can close this window now.": "Це вікно може бути закрито."
 }


Test patch:
diff --git a/cmd/daemon/full_test.go b/cmd/daemon/full_test.go
index 5bcbec1..6380c7b 100644
--- a/cmd/daemon/full_test.go
+++ b/cmd/daemon/full_test.go
@@ -136,7 +136,57 @@ func TestFull(t *testing.T) {
 					return
 				}
 
-				testutils.SendMessage(t, managementInterfaceConn, ">CLIENT:CONNECT,0,1\r\n>CLIENT:ENV,n_clients=0\r\n>CLIENT:ENV,password=\r\n>CLIENT:ENV,untrusted_port=17016\r\n>CLIENT:ENV,untrusted_ip=192.168.65.1\r\n>CLIENT:ENV,common_name=user@example.com\r\n>CLIENT:ENV,username=\r\n>CLIENT:ENV,IV_BS64DL=1\r\n>CLIENT:ENV,IV_SSO=webauth,openurl,crtext\r\n>CLIENT:ENV,IV_GUI_VER=OCmacOS_3.4.4-4629\r\n>CLIENT:ENV,IV_AUTO_SESS=1\r\n>CLIENT:ENV,IV_CIPHERS=AES-128-GCM:AES-192-GCM:AES-256-GCM:CHACHA20-POLY1305\r\n>CLIENT:ENV,IV_MTU=1600\r\n>CLIENT:ENV,IV_PROTO=990\r\n>CLIENT:ENV,IV_TCPNL=1\r\n>CLIENT:ENV,IV_NCP=2\r\n>CLIENT:ENV,IV_PLAT=mac\r\n>CLIENT:ENV,IV_VER=3.8.1\r\n>CLIENT:ENV,tls_serial_hex_0=51:b3:55:90:65:af:71:5c:d5:52:2b:0b:00:14:8d:ee\r\n>CLIENT:ENV,tls_serial_0=108598624241397715647038806614705737198\r\n>CLIENT:ENV,tls_digest_sha256_0=d3:6d:1d:96:f8:bd:7e:e8:db:c4:0f:53:a1:76:f0:ca:9e:78:63:bf:c6:4a:ac:b9:e6:ed:84:62:f5:ac:5d:b8\r\n>CLIENT:ENV,tls_digest_0=b7:73:bd:6c:31:31:49:63:0d:0c:11:6d:0c:13:d0:b4:8f:97:33:7d\r\n>CLIENT:ENV,tls_id_0=CN=user@example.com\r\n>CLIENT:ENV,X509_0_CN=user@example.com\r\n>CLIENT:ENV,tls_serial_hex_1=01:b3:95:f8:1a:9f:9f:fe:7c:27:ad:29:c1:93:23:ae:08:7f:ab:36\r\n>CLIENT:ENV,tls_serial_1=9713888317380397892476539918183380788698917686\r\n>CLIENT:ENV,tls_digest_sha256_1=75:1a:a1:63:bb:e9:c7:f3:e3:bf:e1:08:f1:36:b7:36:90:04:da:dd:b8:78:b1:cf:d5:ac:09:b6:36:31:a7:db\r\n>CLIENT:ENV,tls_digest_1=d4:bc:00:89:e5:01:0c:27:3d:ea:4a:b5:42:8b:f7:3d:19:7a:a2:25\r\n>CLIENT:ENV,tls_id_1=CN=Easy-RSA CA\r\n>CLIENT:ENV,X509_1_CN=Easy-RSA CA\r\n>CLIENT:ENV,remote_port_1=1194\r\n>CLIENT:ENV,local_port_1=1194\r\n>CLIENT:ENV,proto_1=udp\r\n>CLIENT:ENV,daemon_pid=7\r\n>CLIENT:ENV,daemon_start_time=1703401559\r\n>CLIENT:ENV,daemon_log_redirect=0\r\n>CLIENT:ENV,daemon=0\r\n>CLIENT:ENV,verb=3\r\n>CLIENT:ENV,config=/etc/openvpn/openvpn.conf\r\n>CLIENT:ENV,ifconfig_local=100.64.0.1\r\n>CLIENT:ENV,ifconfig_netmask=255.255.255.0\r\n>CLIENT:ENV,script_context=init\r\n>CLIENT:ENV,tun_mtu=1500\r\n>CLIENT:ENV,dev=tun0\r\n>CLIENT:ENV,dev_type=tun\r\n>CLIENT:ENV,redirect_gateway=0\r\n>CLIENT:ENV,END\r\n")
+				msg := strings.Join([]string{
+					">CLIENT:CONNECT,0,1",
+					">CLIENT:ENV,n_clients=0",
+					">CLIENT:ENV,password=",
+					">CLIENT:ENV,untrusted_port=17016",
+					">CLIENT:ENV,untrusted_ip=192.168.65.1",
+					">CLIENT:ENV,common_name=user@example.com",
+					">CLIENT:ENV,username=",
+					">CLIENT:ENV,IV_BS64DL=1",
+					">CLIENT:ENV,IV_SSO=webauth,openurl,crtext",
+					">CLIENT:ENV,IV_GUI_VER=OCmacOS_3.4.4-4629",
+					">CLIENT:ENV,IV_AUTO_SESS=1",
+					">CLIENT:ENV,IV_CIPHERS=AES-128-GCM:AES-192-GCM:AES-256-GCM:CHACHA20-POLY1305",
+					">CLIENT:ENV,IV_MTU=1600",
+					">CLIENT:ENV,IV_PROTO=990",
+					">CLIENT:ENV,IV_TCPNL=1",
+					">CLIENT:ENV,IV_NCP=2",
+					">CLIENT:ENV,IV_PLAT=mac",
+					">CLIENT:ENV,IV_VER=3.8.1",
+					">CLIENT:ENV,tls_serial_hex_0=51:b3:55:90:65:af:71:5c:d5:52:2b:0b:00:14:8d:ee",
+					">CLIENT:ENV,tls_serial_0=108598624241397715647038806614705737198",
+					">CLIENT:ENV,tls_digest_sha256_0=d3:6d:1d:96:f8:bd:7e:e8:db:c4:0f:53:a1:76:f0:ca:9e:78:63:bf:c6:4a:ac:b9:e6:ed:84:62:f5:ac:5d:b8",
+					">CLIENT:ENV,tls_digest_0=b7:73:bd:6c:31:31:49:63:0d:0c:11:6d:0c:13:d0:b4:8f:97:33:7d",
+					">CLIENT:ENV,tls_id_0=CN=user@example.com",
+					">CLIENT:ENV,X509_0_CN=user@example.com",
+					">CLIENT:ENV,tls_serial_hex_1=01:b3:95:f8:1a:9f:9f:fe:7c:27:ad:29:c1:93:23:ae:08:7f:ab:36",
+					">CLIENT:ENV,tls_serial_1=9713888317380397892476539918183380788698917686",
+					">CLIENT:ENV,tls_digest_sha256_1=75:1a:a1:63:bb:e9:c7:f3:e3:bf:e1:08:f1:36:b7:36:90:04:da:dd:b8:78:b1:cf:d5:ac:09:b6:36:31:a7:db",
+					">CLIENT:ENV,tls_digest_1=d4:bc:00:89:e5:01:0c:27:3d:ea:4a:b5:42:8b:f7:3d:19:7a:a2:25",
+					">CLIENT:ENV,tls_id_1=CN=Easy-RSA CA",
+					">CLIENT:ENV,X509_1_CN=Easy-RSA CA",
+					">CLIENT:ENV,remote_port_1=1194",
+					">CLIENT:ENV,local_port_1=1194",
+					">CLIENT:ENV,proto_1=udp",
+					">CLIENT:ENV,daemon_pid=7",
+					">CLIENT:ENV,daemon_start_time=1703401559",
+					">CLIENT:ENV,daemon_log_redirect=0",
+					">CLIENT:ENV,daemon=0",
+					">CLIENT:ENV,verb=3",
+					">CLIENT:ENV,config=/etc/openvpn/openvpn.conf",
+					">CLIENT:ENV,ifconfig_local=100.64.0.1",
+					">CLIENT:ENV,ifconfig_netmask=255.255.255.0",
+					">CLIENT:ENV,script_context=init",
+					">CLIENT:ENV,tun_mtu=1500",
+					">CLIENT:ENV,dev=tun0",
+					">CLIENT:ENV,dev_type=tun",
+					">CLIENT:ENV,redirect_gateway=0",
+					">CLIENT:ENV,END",
+				}, "\r\n")
+
+				testutils.SendMessage(t, managementInterfaceConn, msg+"\r\n")
 
 				authURL := testutils.ReadLine(t, managementInterfaceConn, reader)
 				testutils.SendMessage(t, managementInterfaceConn, "SUCCESS: client-pending-auth command succeeded")
diff --git a/cmd/daemon/root_test.go b/cmd/daemon/root_test.go
index c94c7bf..2839ba3 100644
--- a/cmd/daemon/root_test.go
+++ b/cmd/daemon/root_test.go
@@ -52,17 +52,26 @@ func TestExecuteConfigInvalid(t *testing.T) {
 		},
 		{
 			"invalid log format",
-			[]string{"", "--config=../../config.example.yaml", "--log.format=invalid", "--log.level=warn", "--http.secret=" + testutils.Secret, "--http.listen=127.0.0.1:0"},
+			[]string{
+				"", "--config=../../config.example.yaml", "--log.format=invalid", "--log.level=warn", "--http.secret=" + testutils.Secret,
+				"--http.listen=127.0.0.1:0",
+			},
 			"error configure logging: unknown log format: invalid",
 		},
 		{
 			"invalid log level",
-			[]string{"", "--config=../../config.example.yaml", "--log.format=console", "--log.level=invalid", "--http.secret=" + testutils.Secret, "--http.listen=127.0.0.1:0"},
+			[]string{
+				"", "--config=../../config.example.yaml", "--log.format=console", "--log.level=invalid", "--http.secret=" + testutils.Secret,
+				"--http.listen=127.0.0.1:0",
+			},
 			`invalid value "invalid" for flag -log.level: slog: level string "invalid": unknown name`,
 		},
 		{
 			"error oidc provider",
-			[]string{"", "--config=../../config.example.yaml", "--log.format=console", "--log.level=info", "--http.secret=" + testutils.Secret, "--http.listen=127.0.0.1:0"},
+			[]string{
+				"", "--config=../../config.example.yaml", "--log.format=console", "--log.level=info", "--http.secret=" + testutils.Secret,
+				"--http.listen=127.0.0.1:0",
+			},
 			`error oidc provider`,
 		},
 		{