diff --git a/docs/advanced.rst b/docs/advanced.rst
index 6ee409f..e8c74ec 100644
--- a/docs/advanced.rst
+++ b/docs/advanced.rst
@@ -57,34 +57,34 @@ Available environment variables
==================================
-+--------------------------+------------------------------------------------------------------------------+-------------+-----------------------------+
-| Name | Action | Persistent* | Default |
-+==========================+==============================================================================+=============+=============================+
-| GOKAPI_CONFIG_DIR | Sets the directory for the config file | No | config |
-+--------------------------+------------------------------------------------------------------------------+-------------+-----------------------------+
-| GOKAPI_CONFIG_FILE | Sets the name of the config file | No | config.json |
-+--------------------------+------------------------------------------------------------------------------+-------------+-----------------------------+
-| GOKAPI_DATA_DIR | Sets the directory for the data | Yes | data |
-+--------------------------+------------------------------------------------------------------------------+-------------+-----------------------------+
-| GOKAPI_DB_NAME | Sets the name for the database file | No | gokapi.sqlite |
-+--------------------------+------------------------------------------------------------------------------+-------------+-----------------------------+
-| GOKAPI_LENGTH_ID | Sets the length of the download IDs. Value needs to be 5 or more | Yes | 15 |
-+--------------------------+------------------------------------------------------------------------------+-------------+-----------------------------+
-| GOKAPI_MAX_FILESIZE | Sets the maximum allowed file size in MB | Yes | 102400 (100GB) |
-+--------------------------+------------------------------------------------------------------------------+-------------+-----------------------------+
-| GOKAPI_MAX_MEMORY_UPLOAD | Sets the amount of RAM in MB that can be allocated for an upload. | Yes | 20 |
-| | | | |
-| | Any upload with a size greater than that will be written to a temporary file | | |
-+--------------------------+------------------------------------------------------------------------------+-------------+-----------------------------+
-| GOKAPI_PORT | Sets the webserver port | Yes | 53842 |
-+--------------------------+------------------------------------------------------------------------------+-------------+-----------------------------+
-| TMPDIR | Sets the path which contains temporary files | No | Non-Docker: Default OS path |
-| | | | |
-| | | | Docker: [DATA_DIR] |
-+--------------------------+------------------------------------------------------------------------------+-------------+-----------------------------+
++--------------------------+------------------------------------------------------------------------------+-----------------+-----------------------------+
+| Name | Action | Persistent [*]_ | Default |
++==========================+==============================================================================+=================+=============================+
+| GOKAPI_CONFIG_DIR | Sets the directory for the config file | No | config |
++--------------------------+------------------------------------------------------------------------------+-----------------+-----------------------------+
+| GOKAPI_CONFIG_FILE | Sets the name of the config file | No | config.json |
++--------------------------+------------------------------------------------------------------------------+-----------------+-----------------------------+
+| GOKAPI_DATA_DIR | Sets the directory for the data | Yes | data |
++--------------------------+------------------------------------------------------------------------------+-----------------+-----------------------------+
+| GOKAPI_DB_NAME | Sets the name for the database file | No | gokapi.sqlite |
++--------------------------+------------------------------------------------------------------------------+-----------------+-----------------------------+
+| GOKAPI_LENGTH_ID | Sets the length of the download IDs. Value needs to be 5 or more | Yes | 15 |
++--------------------------+------------------------------------------------------------------------------+-----------------+-----------------------------+
+| GOKAPI_MAX_FILESIZE | Sets the maximum allowed file size in MB | Yes | 102400 (100GB) |
++--------------------------+------------------------------------------------------------------------------+-----------------+-----------------------------+
+| GOKAPI_MAX_MEMORY_UPLOAD | Sets the amount of RAM in MB that can be allocated for an upload. | Yes | 20 |
+| | | | |
+| | Any upload with a size greater than that will be written to a temporary file | | |
++--------------------------+------------------------------------------------------------------------------+-----------------+-----------------------------+
+| GOKAPI_PORT | Sets the webserver port | Yes | 53842 |
++--------------------------+------------------------------------------------------------------------------+-----------------+-----------------------------+
+| TMPDIR | Sets the path which contains temporary files | No | Non-Docker: Default OS path |
+| | | | |
+| | | | Docker: [DATA_DIR] |
++--------------------------+------------------------------------------------------------------------------+-----------------+-----------------------------+
-\* Variables that are persistent must be submitted during the first start when Gokapi creates a new config file. They can be omitted afterwards. Non-persistent variables need to be set on every start.
+.. [*] Variables that are persistent must be submitted during the first start when Gokapi creates a new config file. They can be omitted afterwards. Non-persistent variables need to be set on every start.
diff --git a/docs/conf.py b/docs/conf.py
index 888c93d..a01038b 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -57,3 +57,7 @@ html_static_path = ['']
master_doc = 'index'
#autosectionlabel_prefix_document = True
+
+html_theme_options = {
+ 'navigation_depth': 5,
+}
diff --git a/docs/contributions.rst b/docs/contributions.rst
index a879edd..e1a3931 100644
--- a/docs/contributions.rst
+++ b/docs/contributions.rst
@@ -1,6 +1,7 @@
.. _contributions:
+=============
Contributions
=============
diff --git a/docs/examples.rst b/docs/examples.rst
new file mode 100644
index 0000000..05ca40c
--- /dev/null
+++ b/docs/examples.rst
@@ -0,0 +1,260 @@
+.. _examples:
+
+
+===========================
+Examples
+===========================
+
+
+*********************************
+OpenID Connect Configuration
+*********************************
+
+
+.. _oidcconfig_authelia:
+
+Authelia
+^^^^^^^^^^^^
+
+Server Configuration
+""""""""""""""""""""""
+
+.. note::
+ This guide has been written for version 4.37.5
+
+See the `Authelia documentation `_ on how to setup an OIDC server. An example file would be as followed:
+
+
+.. code-block:: YAML
+
+ identity_providers:
+ oidc:
+ hmac_secret: noz1Aow6Soo9lieyus2E_EXAMPLE_KEY
+ issuer_private_key: |
+ -----BEGIN PRIVATE KEY-----
+ ohf2shae1bahph7ahSh1
+ EXAMPLE_KEY
+ EP3EihoPhei9iingai0v==
+ -----END PRIVATE KEY-----
+ access_token_lifespan: 1h
+ authorize_code_lifespan: 1m
+ id_token_lifespan: 1h
+ refresh_token_lifespan: 90m
+ enable_client_debug_messages: false
+ enforce_pkce: public_clients_only
+ cors:
+ endpoints:
+ - authorization
+ - token
+ - revocation
+ - introspection
+ allowed_origins:
+ - "https://*.your.domain"
+ allowed_origins_from_client_redirect_uris: false
+ clients:
+ - id: gokapi-dev
+ description: Gokapi Example
+ secret: 'AhXeV7_EXAMPLE_KEY'
+ sector_identifier: ''
+ public: false
+ authorization_policy: one_factor
+ consent_mode: pre-configured
+ pre_configured_consent_duration: 1w
+ audience: []
+ scopes:
+ - openid
+ - email
+ - profile
+ - groups
+ redirect_uris:
+ - https://gokapi.website.com/oauth-callback
+ userinfo_signing_algorithm: none
+
+
+* Set ``authorization_policy`` to ``two_factor`` to use OTP or a hardware key.
+* If ``consent_mode`` is ``pre-configured``, the user has the option to remember consent. That way you can use a lower ``Recheck identity`` interval in Gokapi. Logout through the Gokapi interface will not be possible anymore, unless the user logs out their Authelia account. If the option is set to ``explicit``, the user always has to grant the permission aftter the ``Recheck identity`` interval has passed
+* ``scopes`` may exclude ``email`` and ``groups`` if these are not required for authentication, e.g. if all users registered with Authelia may access Gokapi.
+* Make sure ``redirect_uris`` is set to the correct value
+
+
+Gokapi Configuration
+""""""""""""""""""""""
+
++--------------------------+-----------------------------------------------------------+-----------------------------------------+
+| Gokapi Configuration | Input | Example |
++==========================+===========================================================+=========================================+
+| Provider URL | URL to Authelia Server | \https://auth.autheliaserver.com |
++--------------------------+-----------------------------------------------------------+-----------------------------------------+
+| Client ID | Client ID provided in config | gokapi-dev |
++--------------------------+-----------------------------------------------------------+-----------------------------------------+
+| Client Secret | Client secret provided in config | AhXeV7_EXAMPLE_KEY |
++--------------------------+-----------------------------------------------------------+-----------------------------------------+
+| Recheck identity | If mode is ``pre-configured``, use a low interval. | 12 hours |
++--------------------------+-----------------------------------------------------------+-----------------------------------------+
+| Restrict to user | Check this if only certain users shall be allowed to | checked |
+| | | |
+| | access Gokapi admin menu | |
++--------------------------+-----------------------------------------------------------+-----------------------------------------+
+| Scope identifier (user) | Use a scope that is unique to the user, e.g. the username | email |
+| | | |
+| | or the email | |
++--------------------------+-----------------------------------------------------------+-----------------------------------------+
+| Authorised users | Enter all users, separated by semicolon | \*\@company.com;admin\@othercompany.com |
+| | | |
+| | ``*`` can be used as a wildcard | |
++--------------------------+-----------------------------------------------------------+-----------------------------------------+
+| Restrict to group | Check this if only users from certain groups shall be | checked |
+| | | |
+| | allowed to access Gokapi admin menu | |
++--------------------------+-----------------------------------------------------------+-----------------------------------------+
+| Scope identifier (group) | Use a scope that lists the user's groups | groups |
++--------------------------+-----------------------------------------------------------+-----------------------------------------+
+| Authorised groups | Enter all groups, separated by semicolon | dev;admins;gokapi-* |
+| | | |
+| | ``*`` can be used as a wildcard | |
++--------------------------+-----------------------------------------------------------+-----------------------------------------+
+
+
+.. _oidcconfig_keycloak:
+
+Keycloak
+^^^^^^^^^^^^
+
+.. note::
+ This guide has been written for version 23.0.4
+
+
+Server Configuration
+""""""""""""""""""""""
+
+
+Creating the client
+**********************
+
+#. In your realm (default: master) click on ``[Manage] Clients`` and then ``Create Client``
+
+ * Client Type: OpenID Connect
+ * Client ID: a unique ID, ``gokapi-dev`` is used in this example
+#. Click ``Next``
+
+ * Set ``Client authentication`` to on
+ * Only select ``Standard flow`` in ``Authentication flow``
+#. Click ``Next``
+
+ * Add your redirect URL, e.g. ``https://gokapi.website.com/oauth-callback``
+ * Click ``Save``
+#. Click ``Credentials``
+
+ * Note the ``Client Secret``
+
+
+Addding a scope for exposing groups (optional)
+*****************************************************
+
+#. In the realm click on ``[Manage] Client Scopes`` and then ``Create Scope``
+
+ * Name: groups
+ * Type: Optional
+ * Protocol: OpenID Connect
+ * Click ``Save``
+
+#. Click ``Mappers``
+
+ * Click ``Add predefined mapper``
+ * Search for ``groups`` and tick
+ * Click ``Add``
+#. In the realm click on ``[Manage] Clients`` and then ``gokapi-dev``
+ * Click ``Client Scopes``
+ * Click ``Add Client Scope``
+ * Select ``groups`` and click ``Add / Optional``
+
+
+Gokapi Configuration
+""""""""""""""""""""""
+
++--------------------------+-----------------------------------------------------------+--------------------------------------------+
+| Gokapi Configuration | Input | Example |
++==========================+===========================================================+============================================+
+| Provider URL | URL to Keycloak realm | \http://keycloak.server.com/realms/master/ |
++--------------------------+-----------------------------------------------------------+--------------------------------------------+
+| Client ID | Client ID provided | gokapi-dev |
++--------------------------+-----------------------------------------------------------+--------------------------------------------+
+| Client Secret | Client secret provided | AhXeV7_EXAMPLE_KEY |
++--------------------------+-----------------------------------------------------------+--------------------------------------------+
+| Recheck identity | If mode is ``pre-configured``, use a low interval. | 12 hours |
++--------------------------+-----------------------------------------------------------+--------------------------------------------+
+| Restrict to user | Check this if only certain users shall be allowed to | checked |
+| | | |
+| | access Gokapi admin menu | |
++--------------------------+-----------------------------------------------------------+--------------------------------------------+
+| Scope identifier (user) | Use a scope that is unique to the user, e.g. the username | email |
+| | | |
+| | or the email | |
++--------------------------+-----------------------------------------------------------+--------------------------------------------+
+| Authorised users | Enter all users, separated by semicolon | \*\@company.com;admin\@othercompany.com |
+| | | |
+| | ``*`` can be used as a wildcard | |
++--------------------------+-----------------------------------------------------------+--------------------------------------------+
+| Restrict to group | Check this if only users from certain groups shall be | checked |
+| | | |
+| | allowed to access Gokapi admin menu | |
++--------------------------+-----------------------------------------------------------+--------------------------------------------+
+| Scope identifier (group) | Use a scope that lists the user's groups | groups |
++--------------------------+-----------------------------------------------------------+--------------------------------------------+
+| Authorised groups | Enter all groups, separated by semicolon | dev;admins;gokapi-* |
+| | | |
+| | ``*`` can be used as a wildcard | |
++--------------------------+-----------------------------------------------------------+--------------------------------------------+
+
+
+.. note::
+ Logout through the Gokapi interface will not be possible anymore, unless the user logs out their Keycload account.
+
+
+
+.. _oidcconfig_google:
+
+Google
+^^^^^^^^^^^^
+
+Server Configuration
+""""""""""""""""""""""
+
+.. note::
+ This guide has been last updated in January 2024 and is based on `this documentation `_
+
+#. Go to the `Google Cloud Platform Console `_.
+#. From the projects list, select a project or create a new one.
+#. If the APIs & services page isn't already open, open the console left side menu and select APIs & services.
+#. On the left, click Credentials.
+#. Click New Credentials, then select OAuth client ID.
+#. Select Application Type ``Webapplication``
+#. Add the correct Gokapi redirect URL and click Create
+
+
+Gokapi Configuration
+""""""""""""""""""""""
+
++-------------------------+--------------------------------------------------+----------------------------------+
+| Gokapi Configuration | Input | Example |
++=========================+==================================================+==================================+
+| Provider URL | \https://accounts.google.com | \https://accounts.google.com |
++-------------------------+--------------------------------------------------+----------------------------------+
+| Client ID | Client ID provided | XXX.apps.googleusercontent.com |
++-------------------------+--------------------------------------------------+----------------------------------+
+| Client Secret | Client secret provided | AhXeV7_EXAMPLE_KEY |
++-------------------------+--------------------------------------------------+----------------------------------+
+| Recheck identity | Use a low interval. | 12 hours |
++-------------------------+--------------------------------------------------+----------------------------------+
+| Restrict to user | Check this, otherwise any Google user can access | checked |
+| | | |
+| | | |
+| | your Gokapi admin menu | |
++-------------------------+--------------------------------------------------+----------------------------------+
+| Scope identifier (user) | email | email |
++-------------------------+--------------------------------------------------+----------------------------------+
+| Authorised users | Enter all users, separated by semicolon | user\@gmail.com;admin\@gmail.com |
++-------------------------+--------------------------------------------------+----------------------------------+
+| Restrict to group | Unsupported | unchecked |
++-------------------------+--------------------------------------------------+----------------------------------+
+
diff --git a/docs/index.rst b/docs/index.rst
index 1180a3c..a36aaae 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,5 +1,6 @@
.. _index:
+===========================
Gokapi
===========================
@@ -14,11 +15,12 @@ Contents
========
.. toctree::
- :maxdepth: 2
+ :maxdepth: 2
- setup
- usage
- update
- advanced
- contributions
- changelog
+ setup
+ usage
+ update
+ advanced
+ examples
+ contributions
+ changelog
diff --git a/docs/setup.rst b/docs/setup.rst
index 7d27a03..34c6431 100644
--- a/docs/setup.rst
+++ b/docs/setup.rst
@@ -110,30 +110,78 @@ The default authentication method. A single admin user will be generated that au
OAuth2 OpenID Connect
************************
-Use this to authenticate with an OIDC server, eg. Google, Github or an internal server. *Note:* If a user is revoked on the OIDC server, it might take several days to affect the Gokapi session.
+Setup interface
+========================
-+---------------+---------------------------------------------------------------------------------+---------------------------------------------+
-| Option | Expected Entry | Example |
-+===============+=================================================================================+=============================================+
-| Provider URL | The URL to connect to the OIDC server | https://accounts.google.com |
-+---------------+---------------------------------------------------------------------------------+---------------------------------------------+
-| Client ID | Client ID provided by the OIDC server | [random String] |
-+---------------+---------------------------------------------------------------------------------+---------------------------------------------+
-| Client Secret | Client secret provided by the OIDC server | [random String] |
-+---------------+---------------------------------------------------------------------------------+---------------------------------------------+
-| Allowed users | List of users that is allowed to log in as an admin. | gokapiuser@gmail.com;companyadmin@gmail.com |
-| | Separate users with a semicolon or leave blank to allow any authenticated user | |
-+---------------+---------------------------------------------------------------------------------+---------------------------------------------+
+Use this to authenticate with an OIDC server, e.g. Google or an internal server like Authelia or Keycloak.
+
++--------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------+
+| Option | Expected Entry | Example |
++====================+===================================================================================================+=========================================+
+| Provider URL | The URL to connect to the OIDC server | https://accounts.google.com |
++--------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------+
+| Client ID | Client ID provided by the OIDC server | [random String] |
++--------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------+
+| Client Secret | Client secret provided by the OIDC server | [random String] |
++--------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------+
+| Recheck identity | How often to recheck identity. | 12 hours |
+| | | |
+| | If the OIDC server is configured to remember the consent, the user should not receive any further | |
+| | | |
+| | login prompts and it can be ensured, that the user still exist on the server. | |
+| | | |
+| | Otherwise the user has actively grant access every time the identity is rechecked. In that case | |
+| | | |
+| | a higher interval would make sense. | |
++--------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------+
+| Restrict to users | Only allow authorised users to access Gokapi that are listed below | true |
++--------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------+
+| Scope for users | The OIDC scope that contains the user info | email |
++--------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------+
+| Authorised users | List of users that are authorised to log in as an admin, separated by semicolon. | \*\@company.com;admin\@othercompany.com |
+| | | |
+| | ``*`` can be used as a wildcard | |
++--------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------+
+| Restrict to groups | Only allow users that are part of authorised groups to access Gokapi | true |
++--------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------+
+| Scope for groups | The OIDC scope that contains the group info | groups |
++--------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------+
+| Authorised groups | List of groups that are authorised to log their users in as an admin, separated by semicolon. | admin;dev;gokapi-\* |
+| | | |
+| | ``*`` can be used as a wildcard | |
++--------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------+
+
+.. note::
+ If login is restricted to users and groups, both need to be present for a user to access. That means if a user has only one of the two factors, access to the admin menu will be denied.
+
+.. note::
+ A user will be authenticated until the time specified in ``Recheck identity`` has passed. To log out all users immediately, re-run the setup with `--reconfigure`` and complete it. Thereafter all active session will be deleted.
+
+
+.. note::
+ If the OIDC provider is set up to remember consent, it might not be possible to log out through the Gokapi interface
+
+
+
+
+OIDC client/server configuration
+=======================================
When creating an OIDC client on the server, you will need to provide a **redirection URL**. Enter ``http[s]://[gokapi URL]/oauth-callback``
+Tutorial for configuring OIDC servers and the correct client settings for Gokapi can be found in the :ref:`examples` page for the following servers:
+
+* :ref:`oidcconfig_authelia`
+* :ref:`oidcconfig_keycloak`
+* :ref:`oidcconfig_google`
+
You can find a guide on how to create an OIDC client with Github at `Setting up GitHub OAuth 2.0 `_ and a guide for Google at `Setting up OAuth 2.0 `_.
Header Authentication
************************
-Only use this if you are running Gokapi behind a reverse proxy that is capable of authenticating users, e.g. by using Authelia or Authentik.
+Only use this if you are running Gokapi behind a reverse proxy that is capable of authenticating users, e.g. by using Authelia or Authentik. Keycloak does apparently not support this feauture.
Enter the key of the header that returns the username. For Authelia this would be ``Remote-User`` and for Authentik ``X-authentik-username``.
Separate users with a semicolon or leave blank to allow any authenticated user, e.g. ``gokapiuser@gmail.com;companyadmin@gmail.com``
@@ -158,7 +206,8 @@ This option disables Gokapis internal authentication completely, except for API
- ``/uploadComplete``
- ``/uploadStatus``
-**Warning:** This option has potential to be *very* dangerous, only proceed if you know what you are doing!
+.. warning::
+ This option has potential to be *very* dangerous, only proceed if you know what you are doing!
@@ -178,7 +227,10 @@ Stores files locally in the subdirectory ``data`` by default.
Cloudstorage
*********************
-Stores files remotely on an S3 compatible server, e.g. Amazon AWS S3 or Backblaze B2. Please note that files will be stored in plain-text, if no encryption is selected later on.
+.. note::
+ Files will be stored in plain-text, if no encryption is selected later on in the setup
+
+Stores files remotely on an S3 compatible server, e.g. Amazon AWS S3 or Backblaze B2.
It is highly recommended to create a new bucket for Gokapi and set it to "private", so that no file can be downloaded externally. For each download request Gokapi will create a public URL that is only valid for a couple of seconds, so that the file can be downloaded from the external server directly instead of routing it through the local server.
@@ -205,7 +257,8 @@ The following data needs to be provided:
Encryption
""""""""""""""
-*Warning: Encryption has not been audited.*
+.. warning::
+ Encryption has not been audited.
There are three different encryption levels, level 1 encrypts only local files and level 2 encrypts local and files stored on cloud storage (e.g. AWS S3). Decryption of files on remote storage is done client-side, for which a 2MB library needs to be downloaded on first visit. End-to-End encryption (level 3) encrypts the files client-side, therefore even if the Gokapi server has been compromised, no data should leak to the attacker. If the decryption is done client-side, the dowload on mobile devices may be significantly slower.
@@ -228,7 +281,8 @@ You can choose to store the key in the configuration file, which is preferred if
If you are concerned that the configuration file can be read, you can also choose to enter a master password on startup. This needs to be entered in the command line however and Gokapi will not be able to start without it.
-Please note: If you re-run the setup and enable encryption, unencrypted files will stay unencrypted. If you change any configuration related to encryption, all already encrypted files will be deleted.
+.. note::
+ If you re-run the setup and enable encryption, unencrypted files will stay unencrypted. If you change any configuration related to encryption, all already encrypted files will be deleted.
************************
Changing Configuration
@@ -239,6 +293,9 @@ To change any settings set in the initial setup (e.g. your password or storage l
If you are using Docker, shut down the running instance and create a new temporary container with the follwing command: ::
docker run --rm -p 127.0.0.1:53842:53842 -v gokapi-data:/app/data -v gokapi-config:/app/config f0rc3/gokapi:latest /app/gokapi --reconfigure
+
+.. note::
+ After completing the setup, all users will be logged out
diff --git a/internal/configuration/configupgrade/Upgrade.go b/internal/configuration/configupgrade/Upgrade.go
index 2857f7c..6daa346 100644
--- a/internal/configuration/configupgrade/Upgrade.go
+++ b/internal/configuration/configupgrade/Upgrade.go
@@ -11,7 +11,7 @@ import (
)
// CurrentConfigVersion is the version of the configuration structure. Used for upgrading
-const CurrentConfigVersion = 17
+const CurrentConfigVersion = 18
// DoUpgrade checks if an old version is present and updates it to the current version if required
func DoUpgrade(settings *models.Configuration, env *environment.Environment) bool {
@@ -59,10 +59,11 @@ func updateConfig(settings *models.Configuration, env *environment.Environment)
}
}
// < v1.8.2
- if settings.ConfigVersion < 17 {
+ if settings.ConfigVersion < 18 {
if len(settings.Authentication.OAuthUsers) > 0 {
settings.Authentication.OAuthUserScope = "email"
}
+ settings.Authentication.OAuthRecheckInterval = 168
}
}
diff --git a/internal/configuration/setup/Setup_test.go b/internal/configuration/setup/Setup_test.go
index 1888072..e1c80ec 100644
--- a/internal/configuration/setup/Setup_test.go
+++ b/internal/configuration/setup/Setup_test.go
@@ -476,6 +476,7 @@ type setupValues struct {
OAuthScopeGroup setupEntry `form:"oauth_scope_groups"`
OAuthRestrictUser setupEntry `form:"oauth_restrict_users" isBool:"true"`
OAuthRestrictGroups setupEntry `form:"oauth_restrict_groups" isBool:"true"`
+ OAuthRecheckInterval setupEntry `form:"oauth_recheck_interval" isInt:"true"`
AuthHeaderKey setupEntry `form:"auth_headerkey"`
AuthHeaderUsers setupEntry `form:"auth_header_users"`
StorageSelection setupEntry `form:"storage_sel"`
diff --git a/internal/configuration/setup/templates/setup.tmpl b/internal/configuration/setup/templates/setup.tmpl
index 2c2601e..fe8a220 100644
--- a/internal/configuration/setup/templates/setup.tmpl
+++ b/internal/configuration/setup/templates/setup.tmpl
@@ -86,7 +86,7 @@
{{ else }}
- You can now change the Gokapi configuration. For further information please refer to the documentation.
+ You can now change the Gokapi configuration. For further information please refer to the documentation.
{{ end }}
@@ -229,7 +229,7 @@
- Please enter the OIDC client configuration. Multiple users and groups can be separated with a semicolon. The documentationTODO has examples on how to configure for different providers.
+ Please enter the OIDC client configuration. Multiple users and groups can be separated with a semicolon. The documentation has examples on how to configure for different providers.
@@ -246,7 +246,18 @@
-
+
+ Recheck identity every
+
+
Restrict to:
@@ -266,16 +277,15 @@
-
-
+
-
+
{{ if .IsInitialSetup }}
{{ else }}
{{ end }}
-
+
@@ -698,6 +708,7 @@ function TestAWS(button) {
document.getElementById("oauth_scope_groups").value = "{{ .Auth.OAuthGroupScope }}";
document.getElementById("oauth_allowed_groups").disabled = false;
document.getElementById("oauth_allowed_groups").value = "{{ .OAuthGroups }}";
+ document.getElementById("oauth_recheck_interval").value = "{{ .Auth.OAuthRecheckInterval }}";
{{ end }}
break;
case 2:
@@ -721,6 +732,12 @@ function TestAWS(button) {
wizard.on("submit", function(wizard) {
+
+ /* enable inputs again, otherwise they will not be submitted */
+ document.getElementById("oauth_scope_groups").disabled = false;
+ document.getElementById("oauth_scope_users").disabled = false;
+ document.getElementById("oauth_allowed_users").disabled = false;
+ document.getElementById("oauth_allowed_groups").disabled = false;
$.ajax({
type: "POST",
diff --git a/internal/models/Authentication.go b/internal/models/Authentication.go
index 98d313c..ff4bbf9 100644
--- a/internal/models/Authentication.go
+++ b/internal/models/Authentication.go
@@ -2,18 +2,19 @@ package models
// AuthenticationConfig holds configuration on how to authenticate to Gokapi admin menu
type AuthenticationConfig struct {
- Method int `json:"Method"`
- SaltAdmin string `json:"SaltAdmin"`
- SaltFiles string `json:"SaltFiles"`
- Username string `json:"Username"`
- Password string `json:"Password"`
- HeaderKey string `json:"HeaderKey"`
- OAuthProvider string `json:"OauthProvider"`
- OAuthClientId string `json:"OAuthClientId"`
- OAuthClientSecret string `json:"OAuthClientSecret"`
- OAuthUserScope string `json:"OauthUserScope"`
- OAuthGroupScope string `json:"OauthGroupScope"`
- HeaderUsers []string `json:"HeaderUsers"`
- OAuthGroups []string `json:"OAuthGroups"`
- OAuthUsers []string `json:"OauthUsers"`
+ Method int `json:"Method"`
+ SaltAdmin string `json:"SaltAdmin"`
+ SaltFiles string `json:"SaltFiles"`
+ Username string `json:"Username"`
+ Password string `json:"Password"`
+ HeaderKey string `json:"HeaderKey"`
+ OAuthProvider string `json:"OauthProvider"`
+ OAuthClientId string `json:"OAuthClientId"`
+ OAuthClientSecret string `json:"OAuthClientSecret"`
+ OAuthUserScope string `json:"OauthUserScope"`
+ OAuthGroupScope string `json:"OauthGroupScope"`
+ OAuthRecheckInterval int `json:"OAuthRecheckInterval"`
+ HeaderUsers []string `json:"HeaderUsers"`
+ OAuthGroups []string `json:"OAuthGroups"`
+ OAuthUsers []string `json:"OauthUsers"`
}
diff --git a/internal/webserver/Webserver.go b/internal/webserver/Webserver.go
index 4373468..73713bb 100644
--- a/internal/webserver/Webserver.go
+++ b/internal/webserver/Webserver.go
@@ -301,7 +301,12 @@ func showLogin(w http.ResponseWriter, r *http.Request) {
return
}
if configuration.Get().Authentication.Method == authentication.OAuth2 {
- redirect(w, "oauth-login")
+ // If user clicked logout, force consent
+ if r.URL.Query().Has("consent") {
+ redirect(w, "oauth-login?consent=true")
+ } else {
+ redirect(w, "oauth-login")
+ }
return
}
err := r.ParseForm()
@@ -311,7 +316,9 @@ func showLogin(w http.ResponseWriter, r *http.Request) {
failedLogin := false
if pw != "" && user != "" {
if authentication.IsCorrectUsernameAndPassword(user, pw) {
- sessionmanager.CreateSession(w)
+ isOauth := configuration.Get().Authentication.Method == authentication.OAuth2
+ interval := configuration.Get().Authentication.OAuthRecheckInterval
+ sessionmanager.CreateSession(w, isOauth, interval)
redirect(w, "admin")
return
}
diff --git a/internal/webserver/api/Api.go b/internal/webserver/api/Api.go
index ace5bc6..072a85a 100644
--- a/internal/webserver/api/Api.go
+++ b/internal/webserver/api/Api.go
@@ -7,6 +7,7 @@ import (
"github.com/forceu/gokapi/internal/helper"
"github.com/forceu/gokapi/internal/models"
"github.com/forceu/gokapi/internal/storage"
+ "github.com/forceu/gokapi/internal/webserver/authentication"
"github.com/forceu/gokapi/internal/webserver/authentication/sessionmanager"
"github.com/forceu/gokapi/internal/webserver/fileupload"
"net/http"
@@ -285,7 +286,9 @@ func isAuthorisedForApi(w http.ResponseWriter, request apiRequest) bool {
sendError(w, http.StatusBadRequest, "Invalid request")
return false
}
- if IsValidApiKey(request.apiKey, true, perm) || sessionmanager.IsValidSession(w, request.request) {
+ isOauth := configuration.Get().Authentication.Method == authentication.OAuth2
+ interval := configuration.Get().Authentication.OAuthRecheckInterval
+ if IsValidApiKey(request.apiKey, true, perm) || sessionmanager.IsValidSession(w, request.request, isOauth, interval) {
return true
}
sendError(w, http.StatusUnauthorized, "Unauthorized")
diff --git a/internal/webserver/authentication/Authentication.go b/internal/webserver/authentication/Authentication.go
index 2e9dba5..ed24e12 100644
--- a/internal/webserver/authentication/Authentication.go
+++ b/internal/webserver/authentication/Authentication.go
@@ -5,10 +5,12 @@ import (
"encoding/json"
"fmt"
"github.com/forceu/gokapi/internal/configuration"
+ "github.com/forceu/gokapi/internal/helper"
"github.com/forceu/gokapi/internal/models"
"github.com/forceu/gokapi/internal/webserver/authentication/sessionmanager"
"io"
"net/http"
+ "regexp"
"strings"
)
@@ -21,7 +23,7 @@ const Internal = 0
// OAuth2 authentication retrieves the users email with Open Connect ID
const OAuth2 = 1
-// Header authentication relies on a header from a reverse proxy to parse the user name
+// Header authentication relies on a header from a reverse proxy to parse the username
const Header = 2
// Disabled authentication ignores all internal authentication procedures. A reverse proxy needs to restrict access
@@ -67,17 +69,40 @@ func isGrantedHeader(r *http.Request) bool {
func isUserInArray(userEntered string, allowedUsers []string) bool {
for _, allowedUser := range allowedUsers {
- if strings.ToLower(allowedUser) == strings.ToLower(userEntered) {
+ matches, err := matchesWithWildcard(strings.ToLower(allowedUser), strings.ToLower(userEntered))
+ helper.Check(err)
+ if matches {
return true
}
}
return false
}
+func matchesWithWildcard(pattern, input string) (bool, error) {
+ components := strings.Split(pattern, "*")
+ if len(components) == 1 {
+ // if len is 1, there are no *'s, return exact match pattern
+ return regexp.MatchString("^"+pattern+"$", input)
+ }
+ var result strings.Builder
+ for i, literal := range components {
+ // Replace * with .*
+ if i > 0 {
+ result.WriteString(".*")
+ }
+ // Quote any regular expression meta characters in the
+ // literal text.
+ result.WriteString(regexp.QuoteMeta(literal))
+ }
+ return regexp.MatchString("^"+result.String()+"$", input)
+}
+
func isGroupInArray(userGroups []string, allowedGroups []string) bool {
for _, group := range userGroups {
for _, allowedGroup := range allowedGroups {
- if strings.ToLower(allowedGroup) == strings.ToLower(group) {
+ matches, err := matchesWithWildcard(strings.ToLower(allowedGroup), strings.ToLower(group))
+ helper.Check(err)
+ if matches {
return true
}
}
@@ -85,7 +110,7 @@ func isGroupInArray(userGroups []string, allowedGroups []string) bool {
return false
}
-func extractOauthGroups(userInfo OAuthUserInfo, groupScope string) ([]string, error) {
+func extractOauthGroups(userInfo OAuthUserClaims, groupScope string) ([]string, error) {
var claims json.RawMessage
var data map[string]interface{}
@@ -113,7 +138,7 @@ func extractOauthGroups(userInfo OAuthUserInfo, groupScope string) ([]string, er
return groups, nil
}
-func extractFieldValue(userInfo OAuthUserInfo, fieldName string) (string, error) {
+func extractFieldValue(userInfo OAuthUserClaims, fieldName string) (string, error) {
var claims json.RawMessage
err := userInfo.Claims(&claims)
@@ -141,31 +166,41 @@ func extractFieldValue(userInfo OAuthUserInfo, fieldName string) (string, error)
}
// OAuthUserInfo is used to make testing easier. This results in an additional parameter for the subject unfortunately
-type OAuthUserInfo interface {
+type OAuthUserInfo struct {
+ Subject string
+ Email string
+ ClaimsSent OAuthUserClaims
+}
+
+// OAuthUserClaims contains the claims
+type OAuthUserClaims interface {
Claims(v interface{}) error
}
// CheckOauthUserAndRedirect checks if the user is allowed to use the Gokapi instance
-func CheckOauthUserAndRedirect(userInfo OAuthUserInfo, userInfoSubject string, w http.ResponseWriter) error {
+func CheckOauthUserAndRedirect(userInfo OAuthUserInfo, w http.ResponseWriter) error {
var username string
var groups []string
var err error
if authSettings.OAuthUserScope != "" {
- username, err = extractFieldValue(userInfo, authSettings.OAuthUserScope)
- if err != nil {
- return err
+ if authSettings.OAuthUserScope == "email" {
+ username = userInfo.Email
+ } else {
+ username, err = extractFieldValue(userInfo.ClaimsSent, authSettings.OAuthUserScope)
+ if err != nil {
+ return err
+ }
}
}
if authSettings.OAuthGroupScope != "" {
- groups, err = extractOauthGroups(userInfo, authSettings.OAuthGroupScope)
+ groups, err = extractOauthGroups(userInfo.ClaimsSent, authSettings.OAuthGroupScope)
if err != nil {
return err
}
}
- if isValidOauthUser(userInfoSubject, username, groups) {
- // TODO revoke session if oauth is not valid any more
- sessionmanager.CreateSession(w)
+ if isValidOauthUser(userInfo, username, groups) {
+ sessionmanager.CreateSession(w, authSettings.Method == OAuth2, authSettings.OAuthRecheckInterval)
redirect(w, "admin")
return nil
}
@@ -173,8 +208,8 @@ func CheckOauthUserAndRedirect(userInfo OAuthUserInfo, userInfoSubject string, w
return nil
}
-func isValidOauthUser(userInfoSubject string, username string, groups []string) bool {
- if userInfoSubject == "" {
+func isValidOauthUser(userInfo OAuthUserInfo, username string, groups []string) bool {
+ if userInfo.Subject == "" {
return false
}
isValidUser := true
@@ -190,7 +225,7 @@ func isValidOauthUser(userInfoSubject string, username string, groups []string)
// isGrantedSession returns true if the user holds a valid internal session cookie
func isGrantedSession(w http.ResponseWriter, r *http.Request) bool {
- return sessionmanager.IsValidSession(w, r)
+ return sessionmanager.IsValidSession(w, r, authSettings.Method == OAuth2, authSettings.OAuthRecheckInterval)
}
// IsCorrectUsernameAndPassword checks if a provided username and password is correct
@@ -216,7 +251,11 @@ func Logout(w http.ResponseWriter, r *http.Request) {
if authSettings.Method == Internal || authSettings.Method == OAuth2 {
sessionmanager.LogoutSession(w, r)
}
- redirect(w, "login")
+ if authSettings.Method == OAuth2 {
+ redirect(w, "login?consent=true")
+ } else {
+ redirect(w, "login")
+ }
}
// IsLogoutAvailable returns true if a logout button should be shown with the current form of authentication
diff --git a/internal/webserver/authentication/Authentication_test.go b/internal/webserver/authentication/Authentication_test.go
index 568d601..c4c8238 100644
--- a/internal/webserver/authentication/Authentication_test.go
+++ b/internal/webserver/authentication/Authentication_test.go
@@ -3,7 +3,7 @@ package authentication
import (
"encoding/json"
"errors"
- "github.com/coreos/go-oidc/v3/oidc"
+ "fmt"
"github.com/forceu/gokapi/internal/configuration"
"github.com/forceu/gokapi/internal/models"
"github.com/forceu/gokapi/internal/test"
@@ -107,23 +107,90 @@ func TestRedirect(t *testing.T) {
func TestIsValidOauthUser(t *testing.T) {
Init(modelOauth)
- info := oidc.UserInfo{Subject: "randomid"}
- test.IsEqualBool(t, isValidOauthUser(info.Subject, "", []string{}), true)
- test.IsEqualBool(t, isValidOauthUser(info.Subject, "test1", []string{"test2"}), true)
+ info := OAuthUserInfo{Subject: "randomid"}
+ test.IsEqualBool(t, isValidOauthUser(info, "", []string{}), true)
+ test.IsEqualBool(t, isValidOauthUser(info, "test1", []string{"test2"}), true)
authSettings.OAuthUserScope = "user"
authSettings.OAuthUsers = []string{"otheruser"}
- test.IsEqualBool(t, isValidOauthUser(info.Subject, "test1", []string{}), false)
- test.IsEqualBool(t, isValidOauthUser(info.Subject, "otheruser", []string{}), true)
+ test.IsEqualBool(t, isValidOauthUser(info, "test1", []string{}), false)
+ test.IsEqualBool(t, isValidOauthUser(info, "otheruser", []string{}), true)
authSettings.OAuthGroupScope = "group"
authSettings.OAuthGroups = []string{"othergroup"}
- test.IsEqualBool(t, isValidOauthUser(info.Subject, "test1", []string{}), false)
- test.IsEqualBool(t, isValidOauthUser(info.Subject, "otheruser", []string{}), false)
- test.IsEqualBool(t, isValidOauthUser(info.Subject, "test1", []string{"testgroup"}), false)
- test.IsEqualBool(t, isValidOauthUser(info.Subject, "test1", []string{"testgroup", "othergroup"}), false)
- test.IsEqualBool(t, isValidOauthUser(info.Subject, "otheruser", []string{"othergroup"}), true)
- test.IsEqualBool(t, isValidOauthUser(info.Subject, "otheruser", []string{"testgroup", "othergroup"}), true)
+ test.IsEqualBool(t, isValidOauthUser(info, "test1", []string{}), false)
+ test.IsEqualBool(t, isValidOauthUser(info, "otheruser", []string{}), false)
+ test.IsEqualBool(t, isValidOauthUser(info, "test1", []string{"testgroup"}), false)
+ test.IsEqualBool(t, isValidOauthUser(info, "test1", []string{"testgroup", "othergroup"}), false)
+ test.IsEqualBool(t, isValidOauthUser(info, "otheruser", []string{"othergroup"}), true)
+ test.IsEqualBool(t, isValidOauthUser(info, "otheruser", []string{"testgroup", "othergroup"}), true)
info.Subject = ""
- test.IsEqualBool(t, isValidOauthUser(info.Subject, "otheruser", []string{"testgroup", "othergroup"}), false)
+ test.IsEqualBool(t, isValidOauthUser(info, "otheruser", []string{"testgroup", "othergroup"}), false)
+}
+
+func TestWildcardMatch(t *testing.T) {
+ type testPattern struct {
+ Pattern string
+ Input string
+ Result bool
+ }
+ tests := []testPattern{{
+ Pattern: "test",
+ Input: "test",
+ Result: true,
+ }, {
+ Pattern: "test*",
+ Input: "test",
+ Result: true,
+ }, {
+ Pattern: "*test",
+ Input: "test",
+ Result: true,
+ }, {
+ Pattern: "te*st",
+ Input: "test",
+ Result: true,
+ }, {
+ Pattern: "test*",
+ Input: "1test",
+ Result: false,
+ }, {
+ Pattern: "*test",
+ Input: "test1",
+ Result: false,
+ }, {
+ Pattern: "te*st",
+ Input: "teeeeeeeest",
+ Result: true,
+ }, {
+ Pattern: "te*st",
+ Input: "teast",
+ Result: true,
+ }, {
+ Pattern: "te*st",
+ Input: "te@st",
+ Result: true,
+ }, {
+ Pattern: "*@github.com",
+ Input: "email@github.com",
+ Result: true,
+ }, {
+ Pattern: "@github.com",
+ Input: "email@github.com",
+ Result: false,
+ }, {
+ Pattern: "@github.com",
+ Input: "email@gokapi.com",
+ Result: false,
+ }, {
+ Pattern: "*@github.com",
+ Input: "email@gokapi.com",
+ Result: false,
+ }}
+ for _, patternTest := range tests {
+ fmt.Printf("Testing: %s == %s, expecting %v\n", patternTest.Pattern, patternTest.Input, patternTest.Result)
+ result, err := matchesWithWildcard(patternTest.Pattern, patternTest.Input)
+ test.IsNil(t, err)
+ test.IsEqualBool(t, result, patternTest.Result)
+ }
}
func TestLogout(t *testing.T) {
@@ -133,11 +200,10 @@ func TestLogout(t *testing.T) {
}
type testInfo struct {
- Output []byte
- Subject string
+ Output []byte
}
-func (t *testInfo) Claims(v interface{}) error {
+func (t testInfo) Claims(v interface{}) error {
if t.Output == nil {
return errors.New("oidc: claims not set")
}
@@ -146,32 +212,42 @@ func (t *testInfo) Claims(v interface{}) error {
func TestCheckOauthUser(t *testing.T) {
Init(modelOauth)
- info := testInfo{Output: []byte(`{"amr":["pwd","hwk","user","pin","mfa"],"aud":["gokapi-dev"],"auth_time":1705573822,"azp":"gokapi-dev","client_id":"gokapi-dev","email":"test@test.com","email_verified":true,"groups":["admins","dev"],"iat":1705577400,"iss":"https://auth.test.com","name":"gokapi","preferred_username":"gokapi","rat":1705577400,"sub":"944444cf3e-0546-44f2-acfa-a94444444360"}`)}
- output, err := getOuthUserOutput(t, &info, info.Subject)
+ info := OAuthUserInfo{
+ ClaimsSent: testInfo{Output: []byte(`{"amr":["pwd","hwk","user","pin","mfa"],"aud":["gokapi-dev"],"auth_time":1705573822,"azp":"gokapi-dev","client_id":"gokapi-dev","email":"test@test.com","email_verified":true,"groups":["admins","dev"],"iat":1705577400,"iss":"https://auth.test.com","name":"gokapi","preferred_username":"gokapi","rat":1705577400,"sub":"944444cf3e-0546-44f2-acfa-a94444444360"}`)},
+ }
+ output, err := getOuthUserOutput(t, info)
test.IsNil(t, err)
test.IsEqualString(t, redirectsToSite(output), "error-auth")
+
info.Subject = "random"
- output, err = getOuthUserOutput(t, &info, info.Subject)
+ output, err = getOuthUserOutput(t, info)
test.IsNil(t, err)
test.IsEqualString(t, redirectsToSite(output), "admin")
+
+ info.Email = "test@test.com"
authSettings.OAuthUserScope = "email"
authSettings.OAuthUsers = []string{"otheruser"}
- output, err = getOuthUserOutput(t, &info, info.Subject)
+ output, err = getOuthUserOutput(t, info)
test.IsNil(t, err)
test.IsEqualString(t, redirectsToSite(output), "error-auth")
+
authSettings.OAuthUsers = []string{"test@test.com"}
- output, err = getOuthUserOutput(t, &info, info.Subject)
+ output, err = getOuthUserOutput(t, info)
test.IsNil(t, err)
test.IsEqualString(t, redirectsToSite(output), "admin")
+
authSettings.OAuthUsers = []string{"otheruser@test"}
- output, err = getOuthUserOutput(t, &info, info.Subject)
+ output, err = getOuthUserOutput(t, info)
test.IsNil(t, err)
test.IsEqualString(t, redirectsToSite(output), "error-auth")
+
authSettings.OAuthUserScope = "invalidScope"
- output, err = getOuthUserOutput(t, &info, info.Subject)
+ output, err = getOuthUserOutput(t, info)
test.IsNotNil(t, err)
- info.Output = []byte("{invalid")
- output, err = getOuthUserOutput(t, &info, info.Subject)
+
+ newClaims := testInfo{Output: []byte("{invalid")}
+ info.ClaimsSent = newClaims
+ output, err = getOuthUserOutput(t, info)
test.IsNotNil(t, err)
}
@@ -185,10 +261,10 @@ func redirectsToSite(input string) string {
return "other"
}
-func getOuthUserOutput(t *testing.T, info OAuthUserInfo, infoSubject string) (string, error) {
+func getOuthUserOutput(t *testing.T, info OAuthUserInfo) (string, error) {
t.Helper()
w := httptest.NewRecorder()
- err := CheckOauthUserAndRedirect(info, infoSubject, w)
+ err := CheckOauthUserAndRedirect(info, w)
if err != nil {
return "", err
}
diff --git a/internal/webserver/authentication/oauth/Oauth.go b/internal/webserver/authentication/oauth/Oauth.go
index e9321ee..afe42f5 100644
--- a/internal/webserver/authentication/oauth/Oauth.go
+++ b/internal/webserver/authentication/oauth/Oauth.go
@@ -45,8 +45,8 @@ func Init(baseUrl string, credentials models.AuthenticationConfig) {
}
// HandlerLogin is a handler for showing the login screen
-func HandlerLogin(w http.ResponseWriter, r *http.Request) {
- initLogin(w, r, false)
+func HandlerLogin(w http.ResponseWriter, r *http.Request) { // If user clicked logout, force consent
+ initLogin(w, r, r.URL.Query().Has("consent"))
}
func initLogin(w http.ResponseWriter, r *http.Request, showConsentScreen bool) {
@@ -97,7 +97,12 @@ func HandlerCallback(w http.ResponseWriter, r *http.Request) {
showOauthErrorPage(w, r, "Failed to get userinfo: "+err.Error())
return
}
- err = authentication.CheckOauthUserAndRedirect(userInfo, userInfo.Subject, w)
+ info := authentication.OAuthUserInfo{
+ Subject: userInfo.Subject,
+ Email: userInfo.Email,
+ ClaimsSent: userInfo,
+ }
+ err = authentication.CheckOauthUserAndRedirect(info, w)
if err != nil {
showOauthErrorPage(w, r, "Failed to extract scope value: "+err.Error())
}
diff --git a/internal/webserver/authentication/sessionmanager/SessionManager.go b/internal/webserver/authentication/sessionmanager/SessionManager.go
index 36ecb16..f718646 100644
--- a/internal/webserver/authentication/sessionmanager/SessionManager.go
+++ b/internal/webserver/authentication/sessionmanager/SessionManager.go
@@ -20,14 +20,14 @@ const cookieLifeAdmin = 30 * 24 * time.Hour
// IsValidSession checks if the user is submitting a valid session token
// If valid session is found, useSession will be called
// Returns true if authenticated, otherwise false
-func IsValidSession(w http.ResponseWriter, r *http.Request) bool {
+func IsValidSession(w http.ResponseWriter, r *http.Request, isOauth bool, OAuthRecheckInterval int) bool {
cookie, err := r.Cookie("session_token")
if err == nil {
sessionString := cookie.Value
if sessionString != "" {
session, ok := database.GetSession(sessionString)
if ok {
- return useSession(w, sessionString, session)
+ return useSession(w, sessionString, session, isOauth, OAuthRecheckInterval)
}
}
}
@@ -38,13 +38,13 @@ func IsValidSession(w http.ResponseWriter, r *http.Request) bool {
// if it has // been used for more than an hour to limit session hijacking
// Returns true if session is still valid
// Returns false if session is invalid (and deletes it)
-func useSession(w http.ResponseWriter, id string, session models.Session) bool {
+func useSession(w http.ResponseWriter, id string, session models.Session, isOauth bool, OAuthRecheckInterval int) bool {
if session.ValidUntil < time.Now().Unix() {
database.DeleteSession(id)
return false
}
if session.RenewAt < time.Now().Unix() {
- CreateSession(w)
+ CreateSession(w, isOauth, OAuthRecheckInterval)
database.DeleteSession(id)
}
return true
@@ -52,13 +52,18 @@ func useSession(w http.ResponseWriter, id string, session models.Session) bool {
// CreateSession creates a new session - called after login with correct username / password
// If sessions parameter is nil, it will be loaded from config
-func CreateSession(w http.ResponseWriter) {
+func CreateSession(w http.ResponseWriter, isOauth bool, OAuthRecheckInterval int) {
+ timeExpiry := time.Now().Add(cookieLifeAdmin)
+ if isOauth {
+ timeExpiry = time.Now().Add(time.Duration(OAuthRecheckInterval) * time.Hour)
+ }
+
sessionString := helper.GenerateRandomString(60)
database.SaveSession(sessionString, models.Session{
RenewAt: time.Now().Add(12 * time.Hour).Unix(),
- ValidUntil: time.Now().Add(cookieLifeAdmin).Unix(),
+ ValidUntil: timeExpiry.Unix(),
})
- writeSessionCookie(w, sessionString, time.Now().Add(cookieLifeAdmin))
+ writeSessionCookie(w, sessionString, timeExpiry)
}
// LogoutSession logs out user and deletes session
diff --git a/internal/webserver/web/templates/html_error_auth.tmpl b/internal/webserver/web/templates/html_error_auth.tmpl
index fae3b02..5d4de4b 100644
--- a/internal/webserver/web/templates/html_error_auth.tmpl
+++ b/internal/webserver/web/templates/html_error_auth.tmpl
@@ -8,7 +8,7 @@
Unauthorised user
Login with OAuth provider was sucessful, however this user is not authorised by Gokapi.
- Log in as different user
+ Log in as different user
diff --git a/internal/webserver/web/templates/html_error_int_oauth.tmpl b/internal/webserver/web/templates/html_error_int_oauth.tmpl
index 4959ca2..f6b1983 100644
--- a/internal/webserver/web/templates/html_error_int_oauth.tmpl
+++ b/internal/webserver/web/templates/html_error_int_oauth.tmpl
@@ -20,7 +20,7 @@
{{ end}}
{{ .ErrorGenericMessage }}
{{ end }}
- Try again
+ Try again
diff --git a/internal/webserver/web/templates/html_footer.tmpl b/internal/webserver/web/templates/html_footer.tmpl
index 466d374..cdeaca1 100644
--- a/internal/webserver/web/templates/html_footer.tmpl
+++ b/internal/webserver/web/templates/html_footer.tmpl
@@ -2,7 +2,7 @@