identizer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +48 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +218 -0
  5. data/exe/identizer +7 -0
  6. data/lib/identizer/app.rb +111 -0
  7. data/lib/identizer/authorization.rb +21 -0
  8. data/lib/identizer/cli.rb +95 -0
  9. data/lib/identizer/configuration.rb +186 -0
  10. data/lib/identizer/directory_entry.rb +101 -0
  11. data/lib/identizer/docs.rb +22 -0
  12. data/lib/identizer/grant_store.rb +66 -0
  13. data/lib/identizer/handlers/auth0.rb +32 -0
  14. data/lib/identizer/handlers/auth0_management.rb +66 -0
  15. data/lib/identizer/handlers/base.rb +91 -0
  16. data/lib/identizer/handlers/cognito.rb +50 -0
  17. data/lib/identizer/handlers/directory.rb +76 -0
  18. data/lib/identizer/handlers/docs.rb +19 -0
  19. data/lib/identizer/handlers/login.rb +81 -0
  20. data/lib/identizer/handlers/oidc.rb +113 -0
  21. data/lib/identizer/handlers/overview.rb +19 -0
  22. data/lib/identizer/handlers/saml.rb +143 -0
  23. data/lib/identizer/handlers/settings.rb +22 -0
  24. data/lib/identizer/identity.rb +39 -0
  25. data/lib/identizer/identity_store/sqlite_store.rb +63 -0
  26. data/lib/identizer/identity_store.rb +86 -0
  27. data/lib/identizer/ldap/filter.rb +58 -0
  28. data/lib/identizer/ldap/handler.rb +66 -0
  29. data/lib/identizer/ldap/server.rb +178 -0
  30. data/lib/identizer/ldap.rb +16 -0
  31. data/lib/identizer/providers.rb +54 -0
  32. data/lib/identizer/renderer.rb +52 -0
  33. data/lib/identizer/responses.rb +46 -0
  34. data/lib/identizer/saml/encryptor.rb +66 -0
  35. data/lib/identizer/saml/keypair.rb +53 -0
  36. data/lib/identizer/saml/response_builder.rb +138 -0
  37. data/lib/identizer/saml/signer.rb +96 -0
  38. data/lib/identizer/saml.rb +17 -0
  39. data/lib/identizer/server.rb +134 -0
  40. data/lib/identizer/tls.rb +61 -0
  41. data/lib/identizer/token_minter.rb +89 -0
  42. data/lib/identizer/version.rb +5 -0
  43. data/lib/identizer/web/views/directory/index.html.erb +69 -0
  44. data/lib/identizer/web/views/docs/broker-app.html.erb +67 -0
  45. data/lib/identizer/web/views/docs/cognito.html.erb +22 -0
  46. data/lib/identizer/web/views/docs/getting-started.html.erb +28 -0
  47. data/lib/identizer/web/views/docs/index.html.erb +9 -0
  48. data/lib/identizer/web/views/docs/ldap.html.erb +38 -0
  49. data/lib/identizer/web/views/docs/oidc.html.erb +40 -0
  50. data/lib/identizer/web/views/docs/saml.html.erb +52 -0
  51. data/lib/identizer/web/views/docs/tls.html.erb +29 -0
  52. data/lib/identizer/web/views/docs/troubleshooting.html.erb +25 -0
  53. data/lib/identizer/web/views/layout.html.erb +58 -0
  54. data/lib/identizer/web/views/login.html.erb +19 -0
  55. data/lib/identizer/web/views/overview/index.html.erb +40 -0
  56. data/lib/identizer/web/views/settings/index.html.erb +28 -0
  57. data/lib/identizer.rb +64 -0
  58. metadata +282 -0
@@ -0,0 +1,40 @@
1
+ <p class="muted"><a href="<%= prefix %>/docs">&larr; Docs</a></p>
2
+ <article>
3
+ <h1>OIDC integration</h1>
4
+ <p class="lead">Identizer exposes a standard OpenID Connect authorization-code flow with discovery and JWKS.</p>
5
+
6
+ <h2>Endpoints</h2>
7
+ <pre>Issuer <%= h(config.base_url) %>
8
+ Authorize <%= h(config.base_url) %>/v1/authorize
9
+ Token <%= h(config.base_url) %>/v1/token
10
+ Discovery <%= h(config.base_url) %>/.well-known/openid-configuration
11
+ JWKS <%= h(config.base_url) %>/.well-known/jwks.json</pre>
12
+
13
+ <h2>Supported features</h2>
14
+ <ul>
15
+ <li>Authorization-code grant + <strong>PKCE</strong> (S256 / plain)</li>
16
+ <li><strong>Refresh tokens</strong> (<span class="mono">grant_type=refresh_token</span>, rotated on use)</li>
17
+ <li><strong>RP-initiated logout</strong> at <span class="mono"><%= h(config.base_url) %>/v1/logout</span>
18
+ (<span class="mono">post_logout_redirect_uri</span>)</li>
19
+ <li><span class="mono">nonce</span> bound into the id_token; <span class="mono">scope</span> echoed back</li>
20
+ <li>Token <strong>introspection</strong> (<span class="mono">/introspect</span>) and <strong>revocation</strong>
21
+ (<span class="mono">/revoke</span>) — RFC 7662 / 7009</li>
22
+ <li>Optional client registry (<span class="mono">config.clients</span>) — lenient when empty; enables
23
+ <span class="mono">redirect_uri</span> allowlisting</li>
24
+ </ul>
25
+
26
+ <h2>Token signing</h2>
27
+ <p>Default is <strong>HS256</strong> (a shared key) — simplest when your client doesn't verify
28
+ signatures. Switch to <strong>RS256</strong> in <a href="<%= prefix %>/settings">Settings</a> to have
29
+ id_tokens signed with an RSA key and verifiable against the published JWKS.</p>
30
+
31
+ <h2>Claims</h2>
32
+ <p>Directory attributes map to standard claims: <span class="mono">mail→email</span>,
33
+ <span class="mono">givenName→given_name</span>, <span class="mono">sn→family_name</span>,
34
+ <span class="mono">cn→name</span>, <span class="mono">memberOf→groups</span>. The entry's DN is the
35
+ <span class="mono">sub</span>.</p>
36
+
37
+ <h2>Mounting in Rails (dev only)</h2>
38
+ <pre>mount Identizer::App.new =&gt; "/idp" if Rails.env.development?</pre>
39
+ <p>Internal links honour the mount path, so the dashboard works under <span class="mono">/idp</span>.</p>
40
+ </article>
@@ -0,0 +1,52 @@
1
+ <p class="muted"><a href="<%= prefix %>/docs">&larr; Docs</a></p>
2
+ <article>
3
+ <h1>SAML</h1>
4
+ <p class="lead">A real SAML 2.0 Identity Provider: it issues <strong>signed</strong> assertions
5
+ (XML-DSig, RSA-SHA256) that standard SPs verify.</p>
6
+
7
+ <h2>Metadata &amp; endpoints</h2>
8
+ <pre>Metadata URL <%= h(config.base_url) %>/metadata
9
+ SSO URL <%= h(config.base_url) %>/saml/sso (HTTP-Redirect &amp; HTTP-POST)</pre>
10
+ <p>The metadata embeds the IdP signing certificate, so your SP can validate signatures.</p>
11
+
12
+ <p class="muted"><strong>Heads up:</strong> by default any ACS is accepted (dev convenience), so a signed
13
+ assertion will be POSTed wherever the request says. Set
14
+ <span class="mono">config.saml_allowed_acs = ["https://your-app/acs"]</span> to lock it down.</p>
15
+
16
+ <h2>SP-initiated</h2>
17
+ <p>Send an <span class="mono">AuthnRequest</span> to the SSO URL (redirect or POST binding). Identizer
18
+ reads the <span class="mono">AssertionConsumerServiceURL</span> and request <span class="mono">ID</span>,
19
+ shows the login form, then POSTs a signed Response back to your ACS with <span class="mono">InResponseTo</span>
20
+ set and your <span class="mono">RelayState</span> preserved.</p>
21
+
22
+ <h2>IdP-initiated</h2>
23
+ <pre><%= h(config.base_url) %>/saml/sso?acs=https://your-app/acs&amp;audience=https://your-app/metadata</pre>
24
+
25
+ <h2>Attributes</h2>
26
+ <p>The directory entry's attributes are sent as SAML <span class="mono">Attribute</span>s
27
+ (<span class="mono">email, given_name, family_name, groups, ...</span>); the NameID is the email.</p>
28
+
29
+ <h2>Encrypted assertions</h2>
30
+ <p>For SPs that require an <span class="mono">EncryptedAssertion</span>, give Identizer the SP's certificate
31
+ and turn encryption on — the assertion is signed, then encrypted (AES-256-CBC + RSA-OAEP) under that cert:</p>
32
+ <pre>Identizer.configure do |c|
33
+ c.saml_encrypt_assertion = true
34
+ c.saml_sp_certificate = File.read("sp-cert.pem")
35
+ end</pre>
36
+
37
+ <h2>Shibboleth / academic SPs</h2>
38
+ <p>Shibboleth is a SAML 2.0 implementation, so it integrates like any SAML SP. If the SP expects eduPerson
39
+ attribute names, map them:</p>
40
+ <pre>Identizer.configure do |c|
41
+ c.saml_attribute_names = {
42
+ "email" => "urn:oid:0.9.2342.19200300.100.1.3", # mail
43
+ "eppn" => "urn:oid:1.3.6.1.4.1.5923.1.1.1.6" # eduPersonPrincipalName
44
+ }
45
+ end</pre>
46
+ <p>Add an <span class="mono">eppn</span> attribute to the directory entry (Custom attributes) and it is sent
47
+ under that name.</p>
48
+
49
+ <h2>Note</h2>
50
+ <p>Signing requires the <span class="mono">nokogiri</span> gem (a dependency, loaded only when a Response is
51
+ produced). It is a development IdP — convenient, not hardened.</p>
52
+ </article>
@@ -0,0 +1,29 @@
1
+ <p class="muted"><a href="<%= prefix %>/docs">&larr; Docs</a></p>
2
+ <article>
3
+ <h1>TLS &amp; mkcert</h1>
4
+ <p class="lead">Login URLs must be <span class="mono">https</span> — browser popup guards reject plain http —
5
+ so Identizer always listens over TLS.</p>
6
+
7
+ <h2>Default: self-signed</h2>
8
+ <p>With no cert provided, Identizer generates a self-signed cert under the config dir
9
+ (<span class="mono">cert.pem</span> / <span class="mono">key.pem</span>). Browsers will warn; for the
10
+ app's server-to-server calls (token / userinfo / AWS SDK) trust it:</p>
11
+ <pre>export SSL_CERT_FILE=…/cert.pem</pre>
12
+
13
+ <h2>Custom domain</h2>
14
+ <p>Want Identizer to look like a real provider host (e.g. <span class="mono">myco.okta.local</span>)? Run it
15
+ with a domain and tell your machine that name points at localhost:</p>
16
+ <pre>identizer --domain myco.okta.local
17
+ # /etc/hosts
18
+ 127.0.0.1 myco.okta.local</pre>
19
+ <p>The generated self-signed certificate automatically includes the custom domain in its SAN, so HTTPS to
20
+ <span class="mono">https://myco.okta.local:9999</span> validates the hostname. (If you pass your own
21
+ <span class="mono">--tls-cert</span>, make sure that cert covers the domain.)</p>
22
+
23
+ <h2>Recommended: mkcert (locally trusted)</h2>
24
+ <pre>mkcert -install
25
+ mkcert localhost 127.0.0.1
26
+ bundle exec identizer --tls-cert ./localhost+1.pem --tls-key ./localhost+1-key.pem</pre>
27
+ <p>Or set <span class="mono">IDENTIZER_TLS_CERT</span> / <span class="mono">IDENTIZER_TLS_KEY</span>. A
28
+ locally-trusted cert means no browser warnings and no <span class="mono">SSL_CERT_FILE</span> juggling.</p>
29
+ </article>
@@ -0,0 +1,25 @@
1
+ <p class="muted"><a href="<%= prefix %>/docs">&larr; Docs</a></p>
2
+ <article>
3
+ <h1>Troubleshooting</h1>
4
+
5
+ <h2>"Unknown user" / access_denied on login</h2>
6
+ <p>The email isn't in the <a href="<%= prefix %>/directory">Directory</a>, or the password didn't match the
7
+ shared password in <a href="<%= prefix %>/settings">Settings</a>. Add the entry or fix the password.</p>
8
+
9
+ <h2>Browser blocks the popup / cert warnings</h2>
10
+ <p>Login must be https. Use a locally-trusted cert via <a href="<%= prefix %>/docs/tls">mkcert</a>, or accept
11
+ the self-signed cert in the browser once.</p>
12
+
13
+ <h2>App can't reach the token / userinfo endpoint (SSL error)</h2>
14
+ <p>Server-to-server calls must trust the cert. Set <span class="mono">SSL_CERT_FILE</span> for the app
15
+ process, or use mkcert. See <a href="<%= prefix %>/docs/tls">TLS</a>.</p>
16
+
17
+ <h2>id_token signature won't verify</h2>
18
+ <p>The default HS256 key is shared and meant for clients that don't verify. If your client verifies, switch
19
+ to RS256 in <a href="<%= prefix %>/settings">Settings</a> and point it at the JWKS
20
+ (<span class="mono"><%= h(config.base_url) %>/.well-known/jwks.json</span>).</p>
21
+
22
+ <h2>Resetting state</h2>
23
+ <p>Identities and settings are plain JSON under the config dir. Delete
24
+ <span class="mono">config.json</span> / <span class="mono">settings.json</span> to start fresh.</p>
25
+ </article>
@@ -0,0 +1,58 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title><%= h(title) %> · Identizer</title>
7
+ <style>
8
+ :root { --fg:#1d2433; --muted:#667085; --line:#e4e7ec; --accent:#3538cd; --bg:#f9fafb; }
9
+ * { box-sizing: border-box; }
10
+ body { font-family: system-ui, sans-serif; color: var(--fg); margin: 0; background: var(--bg); }
11
+ header { display: flex; align-items: center; gap: 24px; padding: 14px 24px;
12
+ background: #fff; border-bottom: 1px solid var(--line); }
13
+ header .brand { font-weight: 700; letter-spacing: .2px; }
14
+ nav { display: flex; gap: 4px; }
15
+ nav a { padding: 6px 12px; border-radius: 8px; text-decoration: none; color: var(--muted); }
16
+ nav a.active { background: var(--bg); color: var(--accent); font-weight: 600; }
17
+ main { max-width: 880px; margin: 28px auto; padding: 0 24px; }
18
+ h1 { font-size: 22px; margin: 0 0 4px; }
19
+ h2 { font-size: 16px; margin: 28px 0 10px; }
20
+ p.lead { color: var(--muted); margin-top: 0; }
21
+ .card { background: #fff; border: 1px solid var(--line); border-radius: 12px; padding: 18px 20px; margin: 14px 0; }
22
+ table { width: 100%; border-collapse: collapse; }
23
+ th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--line); font-size: 14px; vertical-align: top; }
24
+ th { color: var(--muted); font-weight: 600; }
25
+ code, .mono { font-family: ui-monospace, monospace; font-size: 13px; }
26
+ label { display: block; font-size: 13px; color: var(--muted); margin: 10px 0 4px; }
27
+ input[type=text], input[type=email], input[type=password], textarea, select {
28
+ width: 100%; padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px; font: inherit; }
29
+ textarea { min-height: 70px; font-family: ui-monospace, monospace; }
30
+ button, .btn { padding: 8px 14px; border: 1px solid var(--line); border-radius: 8px; background: #fff;
31
+ cursor: pointer; font: inherit; text-decoration: none; color: var(--fg); display: inline-block; }
32
+ button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
33
+ button.danger { color: #b42318; border-color: #fda29b; }
34
+ .row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
35
+ .tag { display: inline-block; padding: 1px 8px; border-radius: 999px; background: var(--bg);
36
+ border: 1px solid var(--line); font-size: 12px; margin: 1px 2px; }
37
+ .muted { color: var(--muted); }
38
+ .actions { display: flex; gap: 6px; }
39
+ article h2 { border-top: 1px solid var(--line); padding-top: 16px; }
40
+ article pre { background: #0b1020; color: #e6edf3; padding: 14px 16px; border-radius: 10px; overflow: auto; }
41
+ article ul { padding-left: 20px; }
42
+ </style>
43
+ </head>
44
+ <body>
45
+ <header>
46
+ <span class="brand">🔑 Identizer</span>
47
+ <nav>
48
+ <a href="<%= prefix %>/" class="<%= "active" if nav == :overview %>">Overview</a>
49
+ <a href="<%= prefix %>/directory" class="<%= "active" if nav == :directory %>">Directory</a>
50
+ <a href="<%= prefix %>/settings" class="<%= "active" if nav == :settings %>">Settings</a>
51
+ <a href="<%= prefix %>/docs" class="<%= "active" if nav == :docs %>">Docs</a>
52
+ </nav>
53
+ </header>
54
+ <main>
55
+ <%= content %>
56
+ </main>
57
+ </body>
58
+ </html>
@@ -0,0 +1,19 @@
1
+ <!doctype html><html><head><meta charset="utf-8"><title><%= h(title) %></title></head>
2
+ <body style="font-family:sans-serif;max-width:480px;margin:64px auto">
3
+ <h2><%= h(heading) %></h2>
4
+ <p><%= note %></p>
5
+ <form method="<%= h(form_method) %>" action="<%= h(action) %>">
6
+ <% hidden.each do |name, value| -%>
7
+ <input type="hidden" name="<%= h(name) %>" value="<%= h(value) %>">
8
+ <% end -%>
9
+ <input name="email" type="email" required autofocus list="identizer-emails"
10
+ value="<%= h(emails.first) %>" placeholder="user@example.com" style="width:100%;padding:8px">
11
+ <datalist id="identizer-emails"><% emails.each do |email| -%><option value="<%= h(email) %>"><% end -%></datalist>
12
+ <input name="password" type="password" required placeholder="password"
13
+ style="width:100%;padding:8px;margin-top:8px">
14
+ <button type="submit" style="margin-top:16px;padding:8px 16px">Sign in</button>
15
+ </form>
16
+ <% unless config_link.to_s.empty? -%>
17
+ <p style="margin-top:24px"><a href="<%= h(config_link) %>">Configure identities</a></p>
18
+ <% end -%>
19
+ </body></html>
@@ -0,0 +1,40 @@
1
+ <h1>Overview</h1>
2
+ <p class="lead">A local identity provider for developing and testing auth/SSO integrations.</p>
3
+
4
+ <div class="card" style="border-left:4px solid var(--accent)">
5
+ <strong>New here? Three steps:</strong>
6
+ <ol style="margin:8px 0 0;padding-left:20px">
7
+ <li><a href="<%= prefix %>/directory">Add a user</a><% if count.zero? %> — <strong>your directory is empty</strong><% else %> (you have <%= count %>)<% end %>.
8
+ The sign-in password is in <a href="<%= prefix %>/settings">Settings</a>.</li>
9
+ <li>Point your app's SSO config at the values below (or in <a href="<%= prefix %>/docs">Docs</a>).</li>
10
+ <li>Trigger login in your app and sign in as that user.</li>
11
+ </ol>
12
+ <p class="muted" style="margin:8px 0 0">Not sure which protocol you need? See
13
+ <a href="<%= prefix %>/docs/getting-started">Docs → Getting started</a>.</p>
14
+ </div>
15
+
16
+ <div class="card">
17
+ <table>
18
+ <tr><th>Base URL</th><td class="mono"><%= h(config.base_url) %></td></tr>
19
+ <tr><th>Issuer</th><td class="mono"><%= h(config.issuer) %></td></tr>
20
+ <tr><th>Directory entries</th><td><%= count %> &middot; <a href="<%= prefix %>/directory">manage</a></td></tr>
21
+ <tr><th>Token signing</th><td><%= h(config.signing.to_s.upcase) %> &middot; <a href="<%= prefix %>/settings">change</a></td></tr>
22
+ <tr><th>LDAP base DN</th><td class="mono"><%= h(config.ldap_base_dn) %></td></tr>
23
+ </table>
24
+ </div>
25
+
26
+ <h2>Provider setup cheatsheet</h2>
27
+ <p class="muted">Values to paste into your app's integration form. The runtime is identical for all of them.</p>
28
+ <% config.providers.each do |provider| %>
29
+ <div class="card">
30
+ <strong><%= h(provider[:title]) %></strong>
31
+ <% if provider[:note] %><p class="muted"><%= h(provider[:note]) %></p><% end %>
32
+ <table>
33
+ <% provider[:fields].each do |label, value| %>
34
+ <tr><th><%= h(label) %></th><td class="mono"><%= h(value) %></td></tr>
35
+ <% end %>
36
+ </table>
37
+ </div>
38
+ <% end %>
39
+
40
+ <p class="muted">New here? Read the <a href="<%= prefix %>/docs">docs</a>.</p>
@@ -0,0 +1,28 @@
1
+ <h1>Settings</h1>
2
+ <p class="lead">Runtime settings. Changes apply immediately and are persisted to
3
+ <span class="mono">settings.json</span> under the config dir.</p>
4
+
5
+ <form class="card" method="post" action="<%= prefix %>/settings">
6
+ <label>Shared sign-in password</label>
7
+ <input type="text" name="shared_password" value="<%= h(config.shared_password) %>">
8
+ <p class="muted">Type this on the login form to succeed; anything else exercises the provider's error path.</p>
9
+
10
+ <label>Token signing</label>
11
+ <select name="signing">
12
+ <option value="hs256" <%= "selected" unless config.rs256? %>>HS256 — shared key (simplest; clients don't verify)</option>
13
+ <option value="rs256" <%= "selected" if config.rs256? %>>RS256 — RSA + published JWKS (for clients that verify)</option>
14
+ </select>
15
+
16
+ <p><button class="primary" type="submit">Save settings</button></p>
17
+ </form>
18
+
19
+ <div class="card">
20
+ <h2 style="margin-top:0;border:0;padding:0">Boot-time (read-only)</h2>
21
+ <table>
22
+ <tr><th>Listen</th><td class="mono"><%= h(config.host) %>:<%= config.port %></td></tr>
23
+ <tr><th>Base URL</th><td class="mono"><%= h(config.base_url) %></td></tr>
24
+ <tr><th>Config dir</th><td class="mono"><%= h(config.config_dir) %></td></tr>
25
+ <tr><th>LDAP base DN</th><td class="mono"><%= h(config.ldap_base_dn) %></td></tr>
26
+ </table>
27
+ <p class="muted">These are set via CLI flags / env at boot.</p>
28
+ </div>
data/lib/identizer.rb ADDED
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "fileutils"
5
+ require "securerandom"
6
+ require "cgi"
7
+ require "openssl"
8
+ require "base64"
9
+ require "digest"
10
+ require "rack"
11
+ require "jwt"
12
+
13
+ require_relative "identizer/version"
14
+ require_relative "identizer/identity"
15
+ require_relative "identizer/authorization"
16
+ require_relative "identizer/grant_store"
17
+ require_relative "identizer/providers"
18
+ require_relative "identizer/directory_entry"
19
+ require_relative "identizer/identity_store"
20
+ require_relative "identizer/configuration"
21
+ require_relative "identizer/token_minter"
22
+ require_relative "identizer/responses"
23
+ require_relative "identizer/renderer"
24
+ require_relative "identizer/docs"
25
+ require_relative "identizer/handlers/base"
26
+ require_relative "identizer/handlers/overview"
27
+ require_relative "identizer/handlers/directory"
28
+ require_relative "identizer/handlers/settings"
29
+ require_relative "identizer/handlers/docs"
30
+ require_relative "identizer/handlers/login"
31
+ require_relative "identizer/handlers/cognito"
32
+ require_relative "identizer/handlers/auth0"
33
+ require_relative "identizer/handlers/auth0_management"
34
+ require_relative "identizer/handlers/oidc"
35
+ require_relative "identizer/handlers/saml"
36
+ require_relative "identizer/app"
37
+ require_relative "identizer/tls"
38
+ require_relative "identizer/server"
39
+
40
+ # Identizer is a local identity provider for developing and testing auth/SSO
41
+ # integrations. It speaks OIDC, OAuth2 and emulates an AWS Cognito / Auth0 SSO
42
+ # broker, with a pluggable identity store.
43
+ module Identizer
44
+ class << self
45
+ def configure
46
+ yield(configuration) if block_given?
47
+ configuration
48
+ end
49
+
50
+ def configuration
51
+ @configuration ||= Configuration.new
52
+ end
53
+
54
+ def reset_configuration!
55
+ @configuration = Configuration.new
56
+ end
57
+
58
+ # Build a fresh Rack app for the given configuration (defaults to the
59
+ # process-wide configuration).
60
+ def app(config = configuration)
61
+ App.new(config)
62
+ end
63
+ end
64
+ end
metadata ADDED
@@ -0,0 +1,282 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: identizer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Alex Andreiev
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2026-06-27 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: jwt
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '2.0'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '4'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '2.0'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '4'
32
+ - !ruby/object:Gem::Dependency
33
+ name: net-ldap
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: '0.19'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - "~>"
44
+ - !ruby/object:Gem::Version
45
+ version: '0.19'
46
+ - !ruby/object:Gem::Dependency
47
+ name: nokogiri
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '1.15'
53
+ type: :runtime
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '1.15'
60
+ - !ruby/object:Gem::Dependency
61
+ name: rack
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '2.2'
67
+ - - "<"
68
+ - !ruby/object:Gem::Version
69
+ version: '4'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '2.2'
77
+ - - "<"
78
+ - !ruby/object:Gem::Version
79
+ version: '4'
80
+ - !ruby/object:Gem::Dependency
81
+ name: webrick
82
+ requirement: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - "~>"
85
+ - !ruby/object:Gem::Version
86
+ version: '1.7'
87
+ type: :runtime
88
+ prerelease: false
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - "~>"
92
+ - !ruby/object:Gem::Version
93
+ version: '1.7'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rack-test
96
+ requirement: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - "~>"
99
+ - !ruby/object:Gem::Version
100
+ version: '2.1'
101
+ type: :development
102
+ prerelease: false
103
+ version_requirements: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - "~>"
106
+ - !ruby/object:Gem::Version
107
+ version: '2.1'
108
+ - !ruby/object:Gem::Dependency
109
+ name: rake
110
+ requirement: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - "~>"
113
+ - !ruby/object:Gem::Version
114
+ version: '13.0'
115
+ type: :development
116
+ prerelease: false
117
+ version_requirements: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - "~>"
120
+ - !ruby/object:Gem::Version
121
+ version: '13.0'
122
+ - !ruby/object:Gem::Dependency
123
+ name: rspec
124
+ requirement: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - "~>"
127
+ - !ruby/object:Gem::Version
128
+ version: '3.13'
129
+ type: :development
130
+ prerelease: false
131
+ version_requirements: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - "~>"
134
+ - !ruby/object:Gem::Version
135
+ version: '3.13'
136
+ - !ruby/object:Gem::Dependency
137
+ name: rubocop
138
+ requirement: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - "~>"
141
+ - !ruby/object:Gem::Version
142
+ version: '1.65'
143
+ type: :development
144
+ prerelease: false
145
+ version_requirements: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - "~>"
148
+ - !ruby/object:Gem::Version
149
+ version: '1.65'
150
+ - !ruby/object:Gem::Dependency
151
+ name: rubocop-rspec
152
+ requirement: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - "~>"
155
+ - !ruby/object:Gem::Version
156
+ version: '3.0'
157
+ type: :development
158
+ prerelease: false
159
+ version_requirements: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - "~>"
162
+ - !ruby/object:Gem::Version
163
+ version: '3.0'
164
+ - !ruby/object:Gem::Dependency
165
+ name: ruby-saml
166
+ requirement: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - "~>"
169
+ - !ruby/object:Gem::Version
170
+ version: '1.17'
171
+ type: :development
172
+ prerelease: false
173
+ version_requirements: !ruby/object:Gem::Requirement
174
+ requirements:
175
+ - - "~>"
176
+ - !ruby/object:Gem::Version
177
+ version: '1.17'
178
+ - !ruby/object:Gem::Dependency
179
+ name: sqlite3
180
+ requirement: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - "~>"
183
+ - !ruby/object:Gem::Version
184
+ version: '2.0'
185
+ type: :development
186
+ prerelease: false
187
+ version_requirements: !ruby/object:Gem::Requirement
188
+ requirements:
189
+ - - "~>"
190
+ - !ruby/object:Gem::Version
191
+ version: '2.0'
192
+ description: |
193
+ Identizer boots a local identity provider that emulates OIDC, OAuth2 and an
194
+ AWS Cognito / Auth0 SSO broker, so the whole popup -> callback -> login round
195
+ trip can be configured and run locally without real tenants. Installable as a
196
+ gem, runnable standalone or mountable as a Rack app in tests.
197
+ executables:
198
+ - identizer
199
+ extensions: []
200
+ extra_rdoc_files: []
201
+ files:
202
+ - CHANGELOG.md
203
+ - LICENSE.txt
204
+ - README.md
205
+ - exe/identizer
206
+ - lib/identizer.rb
207
+ - lib/identizer/app.rb
208
+ - lib/identizer/authorization.rb
209
+ - lib/identizer/cli.rb
210
+ - lib/identizer/configuration.rb
211
+ - lib/identizer/directory_entry.rb
212
+ - lib/identizer/docs.rb
213
+ - lib/identizer/grant_store.rb
214
+ - lib/identizer/handlers/auth0.rb
215
+ - lib/identizer/handlers/auth0_management.rb
216
+ - lib/identizer/handlers/base.rb
217
+ - lib/identizer/handlers/cognito.rb
218
+ - lib/identizer/handlers/directory.rb
219
+ - lib/identizer/handlers/docs.rb
220
+ - lib/identizer/handlers/login.rb
221
+ - lib/identizer/handlers/oidc.rb
222
+ - lib/identizer/handlers/overview.rb
223
+ - lib/identizer/handlers/saml.rb
224
+ - lib/identizer/handlers/settings.rb
225
+ - lib/identizer/identity.rb
226
+ - lib/identizer/identity_store.rb
227
+ - lib/identizer/identity_store/sqlite_store.rb
228
+ - lib/identizer/ldap.rb
229
+ - lib/identizer/ldap/filter.rb
230
+ - lib/identizer/ldap/handler.rb
231
+ - lib/identizer/ldap/server.rb
232
+ - lib/identizer/providers.rb
233
+ - lib/identizer/renderer.rb
234
+ - lib/identizer/responses.rb
235
+ - lib/identizer/saml.rb
236
+ - lib/identizer/saml/encryptor.rb
237
+ - lib/identizer/saml/keypair.rb
238
+ - lib/identizer/saml/response_builder.rb
239
+ - lib/identizer/saml/signer.rb
240
+ - lib/identizer/server.rb
241
+ - lib/identizer/tls.rb
242
+ - lib/identizer/token_minter.rb
243
+ - lib/identizer/version.rb
244
+ - lib/identizer/web/views/directory/index.html.erb
245
+ - lib/identizer/web/views/docs/broker-app.html.erb
246
+ - lib/identizer/web/views/docs/cognito.html.erb
247
+ - lib/identizer/web/views/docs/getting-started.html.erb
248
+ - lib/identizer/web/views/docs/index.html.erb
249
+ - lib/identizer/web/views/docs/ldap.html.erb
250
+ - lib/identizer/web/views/docs/oidc.html.erb
251
+ - lib/identizer/web/views/docs/saml.html.erb
252
+ - lib/identizer/web/views/docs/tls.html.erb
253
+ - lib/identizer/web/views/docs/troubleshooting.html.erb
254
+ - lib/identizer/web/views/layout.html.erb
255
+ - lib/identizer/web/views/login.html.erb
256
+ - lib/identizer/web/views/overview/index.html.erb
257
+ - lib/identizer/web/views/settings/index.html.erb
258
+ homepage: https://github.com/alex-andreiev/identizer
259
+ licenses:
260
+ - MIT
261
+ metadata:
262
+ source_code_uri: https://github.com/alex-andreiev/identizer
263
+ changelog_uri: https://github.com/alex-andreiev/identizer/blob/main/CHANGELOG.md
264
+ rubygems_mfa_required: 'true'
265
+ rdoc_options: []
266
+ require_paths:
267
+ - lib
268
+ required_ruby_version: !ruby/object:Gem::Requirement
269
+ requirements:
270
+ - - ">="
271
+ - !ruby/object:Gem::Version
272
+ version: '3.2'
273
+ required_rubygems_version: !ruby/object:Gem::Requirement
274
+ requirements:
275
+ - - ">="
276
+ - !ruby/object:Gem::Version
277
+ version: '0'
278
+ requirements: []
279
+ rubygems_version: 3.6.2
280
+ specification_version: 4
281
+ summary: A local identity provider for developing and testing auth/SSO integrations.
282
+ test_files: []