- <%= form.label :id_token_ttl, "ID Token TTL", class: "block text-sm font-medium text-gray-700" %>
+ <%= form.label :id_token_ttl, "ID Token TTL", class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<%= form.text_field :id_token_ttl,
value: application.id_token_ttl || "1h",
placeholder: "e.g., 1h, 30m, 3600",
- class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm font-mono" %>
-
+ class: "mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm font-mono" %>
+
Range: 5m - 24h
Default: 1h
<% if application.id_token_ttl.present? %>
@@ -264,16 +271,16 @@
Understanding Token Types & Session Length
-
+
-
Token Types:
+
Token Types:
Access Token: Used to access protected resources (APIs). Shorter lifetime = more secure. Users won't notice automatic refreshes.
Refresh Token: Used to get new access tokens without re-authentication. Each refresh issues a new refresh token (token rotation).
ID Token: Contains user identity information (JWT). Should match access token lifetime in most cases.
-
-
How Session Length Works:
+
+
How Session Length Works:
Refresh Token TTL = Maximum Inactivity Period
Because refresh tokens are automatically rotated (new token = new expiry), active users can stay logged in indefinitely. The TTL controls how long they can be inactive before requiring re-authentication.
@@ -284,21 +291,21 @@
-
-
Forcing Re-Authentication:
+
+
Forcing Re-Authentication:
Because of token rotation, there's no way to force periodic re-authentication using TTL settings alone. Active users can stay logged in indefinitely by refreshing tokens before they expire.
-
To enforce absolute session limits: Clients can include the max_age parameter in their authorization requests to require re-authentication after a specific time, regardless of token rotation.
+
To enforce absolute session limits: Clients can include the max_age parameter in their authorization requests to require re-authentication after a specific time, regardless of token rotation.
-
Example: A banking app might set max_age=900 (15 minutes) in the authorization request to force re-authentication every 15 minutes, even if refresh tokens are still valid.
+
Example: A banking app might set max_age=900 (15 minutes) in the authorization request to force re-authentication every 15 minutes, even if refresh tokens are still valid.
-
-
Common Configurations:
+
+
Common Configurations:
- - Banking/High Security: Access TTL =
5m, Refresh TTL = 5m → Re-auth every 5 minutes
- - Corporate Tools: Access TTL =
1h, Refresh TTL = 8h → Re-auth after 8 hours inactive
- - Personal Apps: Access TTL =
1h, Refresh TTL = 30d → Re-auth after 30 days inactive
+ - Banking/High Security: Access TTL =
5m, Refresh TTL = 5m → Re-auth every 5 minutes
+ - Corporate Tools: Access TTL =
1h, Refresh TTL = 8h → Re-auth after 8 hours inactive
+ - Personal Apps: Access TTL =
1h, Refresh TTL = 30d → Re-auth after 30 days inactive
@@ -307,30 +314,30 @@
-
-
Forward Auth Configuration
+
+
Forward Auth Configuration
- <%= form.label :domain_pattern, "Domain Pattern", class: "block text-sm font-medium text-gray-700" %>
- <%= form.text_field :domain_pattern, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm font-mono", placeholder: "*.example.com or app.example.com" %>
-
Domain pattern to match. Use * for wildcard subdomains (e.g., *.example.com matches app.example.com, api.example.com, etc.)
+ <%= form.label :domain_pattern, "Domain Pattern", class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
+ <%= form.text_field :domain_pattern, class: "mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm font-mono", placeholder: "*.example.com or app.example.com" %>
+
Domain pattern to match. Use * for wildcard subdomains (e.g., *.example.com matches app.example.com, api.example.com, etc.)
- <%= form.label :headers_config, "Custom Headers Configuration (JSON)", class: "block text-sm font-medium text-gray-700" %>
+ <%= form.label :headers_config, "Custom Headers Configuration (JSON)", class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<%= form.text_area :headers_config, value: (application.headers_config.present? && application.headers_config.any? ? JSON.pretty_generate(application.headers_config) : ""), rows: 10,
- class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm font-mono",
+ class: "mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm font-mono",
placeholder: '{"user": "Remote-User", "groups": "Remote-Groups"}',
data: {
action: "input->json-validator#validate blur->json-validator#format",
json_validator_target: "textarea"
} %>
-
+
Optional: Customize header names sent to your application.
-
-
+
+
Default headers: X-Remote-User, X-Remote-Email, X-Remote-Name, X-Remote-Username, X-Remote-Groups, X-Remote-Admin
@@ -338,13 +345,13 @@
Show available header keys and what data they send
-
user - User's email address
-
email - User's email address
-
name - User's display name (falls back to email if not set)
-
username - User's login username (only sent if set)
-
groups - Comma-separated list of group names (e.g., "admin,developers")
-
admin - "true" or "false" indicating admin status
-
Example: {"user": "Remote-User", "groups": "Remote-Groups", "username": "Remote-Username"}
+
user - User's email address
+
email - User's email address
+
name - User's display name (falls back to email if not set)
+
username - User's login username (only sent if set)
+
groups - Comma-separated list of group names (e.g., "admin,developers")
+
admin - "true" or "false" indicating admin status
+
Example: {"user": "Remote-User", "groups": "Remote-Groups", "username": "Remote-Username"}
Need custom user fields? Add them to user's custom_claims for OIDC tokens
@@ -353,31 +360,30 @@
- <%= form.label :group_ids, "Allowed Groups (Optional)", class: "block text-sm font-medium text-gray-700" %>
-
+ <%= form.label :group_ids, "Allowed Groups (Optional)", class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
+
<% if @available_groups.any? %>
<% @available_groups.each do |group| %>
- <%= check_box_tag "application[group_ids][]", group.id, application.allowed_groups.include?(group), class: "h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" %>
- <%= label_tag "application_group_ids_#{group.id}", group.name, class: "ml-2 text-sm text-gray-900" %>
- (<%= pluralize(group.users.count, "member") %>)
+ <%= check_box_tag "application[group_ids][]", group.id, application.allowed_groups.include?(group), class: "h-4 w-4 rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500" %>
+ <%= label_tag "application_group_ids_#{group.id}", group.name, class: "ml-2 text-sm text-gray-900 dark:text-gray-100" %>
+ (<%= pluralize(group.users.count, "member") %>)
<% end %>
<% else %>
-
No groups available. Create groups first to restrict access.
+
No groups available. Create groups first to restrict access.
<% end %>
-
If no groups are selected, all active users can access this application.
+
If no groups are selected, all active users can access this application.
- <%= form.check_box :active, class: "h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" %>
- <%= form.label :active, "Active", class: "ml-2 block text-sm text-gray-900" %>
+ <%= form.check_box :active, class: "h-4 w-4 rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500" %>
+ <%= form.label :active, "Active", class: "ml-2 block text-sm text-gray-900 dark:text-gray-100" %>
<%= form.submit application.persisted? ? "Update Application" : "Create Application", class: "rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600" %>
- <%= link_to "Cancel", admin_applications_path, class: "rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50" %>
+ <%= link_to "Cancel", admin_applications_path, class: "rounded-md bg-white dark:bg-gray-700 px-3 py-2 text-sm font-semibold text-gray-900 dark:text-gray-200 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600" %>
<% end %>
-
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..a126d6a
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,44 @@
+{
+ "name": "clinch",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "clinch",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "@tailwindcss/forms": "^0.5.11"
+ }
+ },
+ "node_modules/@tailwindcss/forms": {
+ "version": "0.5.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.11.tgz",
+ "integrity": "sha512-h9wegbZDPurxG22xZSoWtdzc41/OlNEUQERNqI/0fOwa2aVlWGu7C35E/x6LDyD3lgtztFSSjKZyuVM0hxhbgA==",
+ "license": "MIT",
+ "dependencies": {
+ "mini-svg-data-uri": "^1.2.3"
+ },
+ "peerDependencies": {
+ "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1"
+ }
+ },
+ "node_modules/mini-svg-data-uri": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
+ "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
+ "license": "MIT",
+ "bin": {
+ "mini-svg-data-uri": "cli.js"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz",
+ "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==",
+ "license": "MIT",
+ "peer": true
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..87203d3
--- /dev/null
+++ b/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "clinch",
+ "version": "1.0.0",
+ "description": "> [!NOTE] > This software is experimental. If you'd like to try it out, find bugs, security flaws and improvements, please do.",
+ "main": "index.js",
+ "directories": {
+ "doc": "docs",
+ "lib": "lib",
+ "test": "test"
+ },
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "ssh://git@git.booko.info:2222/dkam/clinch.git"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "type": "commonjs",
+ "dependencies": {
+ "@tailwindcss/forms": "^0.5.11"
+ }
+}