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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +48 -0
- data/LICENSE.txt +21 -0
- data/README.md +218 -0
- data/exe/identizer +7 -0
- data/lib/identizer/app.rb +111 -0
- data/lib/identizer/authorization.rb +21 -0
- data/lib/identizer/cli.rb +95 -0
- data/lib/identizer/configuration.rb +186 -0
- data/lib/identizer/directory_entry.rb +101 -0
- data/lib/identizer/docs.rb +22 -0
- data/lib/identizer/grant_store.rb +66 -0
- data/lib/identizer/handlers/auth0.rb +32 -0
- data/lib/identizer/handlers/auth0_management.rb +66 -0
- data/lib/identizer/handlers/base.rb +91 -0
- data/lib/identizer/handlers/cognito.rb +50 -0
- data/lib/identizer/handlers/directory.rb +76 -0
- data/lib/identizer/handlers/docs.rb +19 -0
- data/lib/identizer/handlers/login.rb +81 -0
- data/lib/identizer/handlers/oidc.rb +113 -0
- data/lib/identizer/handlers/overview.rb +19 -0
- data/lib/identizer/handlers/saml.rb +143 -0
- data/lib/identizer/handlers/settings.rb +22 -0
- data/lib/identizer/identity.rb +39 -0
- data/lib/identizer/identity_store/sqlite_store.rb +63 -0
- data/lib/identizer/identity_store.rb +86 -0
- data/lib/identizer/ldap/filter.rb +58 -0
- data/lib/identizer/ldap/handler.rb +66 -0
- data/lib/identizer/ldap/server.rb +178 -0
- data/lib/identizer/ldap.rb +16 -0
- data/lib/identizer/providers.rb +54 -0
- data/lib/identizer/renderer.rb +52 -0
- data/lib/identizer/responses.rb +46 -0
- data/lib/identizer/saml/encryptor.rb +66 -0
- data/lib/identizer/saml/keypair.rb +53 -0
- data/lib/identizer/saml/response_builder.rb +138 -0
- data/lib/identizer/saml/signer.rb +96 -0
- data/lib/identizer/saml.rb +17 -0
- data/lib/identizer/server.rb +134 -0
- data/lib/identizer/tls.rb +61 -0
- data/lib/identizer/token_minter.rb +89 -0
- data/lib/identizer/version.rb +5 -0
- data/lib/identizer/web/views/directory/index.html.erb +69 -0
- data/lib/identizer/web/views/docs/broker-app.html.erb +67 -0
- data/lib/identizer/web/views/docs/cognito.html.erb +22 -0
- data/lib/identizer/web/views/docs/getting-started.html.erb +28 -0
- data/lib/identizer/web/views/docs/index.html.erb +9 -0
- data/lib/identizer/web/views/docs/ldap.html.erb +38 -0
- data/lib/identizer/web/views/docs/oidc.html.erb +40 -0
- data/lib/identizer/web/views/docs/saml.html.erb +52 -0
- data/lib/identizer/web/views/docs/tls.html.erb +29 -0
- data/lib/identizer/web/views/docs/troubleshooting.html.erb +25 -0
- data/lib/identizer/web/views/layout.html.erb +58 -0
- data/lib/identizer/web/views/login.html.erb +19 -0
- data/lib/identizer/web/views/overview/index.html.erb +40 -0
- data/lib/identizer/web/views/settings/index.html.erb +28 -0
- data/lib/identizer.rb +64 -0
- metadata +282 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<p class="muted"><a href="<%= prefix %>/docs">← 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 => "/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">← 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 & endpoints</h2>
|
|
8
|
+
<pre>Metadata URL <%= h(config.base_url) %>/metadata
|
|
9
|
+
SSO URL <%= h(config.base_url) %>/saml/sso (HTTP-Redirect & 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&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">← Docs</a></p>
|
|
2
|
+
<article>
|
|
3
|
+
<h1>TLS & 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">← 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 %> · <a href="<%= prefix %>/directory">manage</a></td></tr>
|
|
21
|
+
<tr><th>Token signing</th><td><%= h(config.signing.to_s.upcase) %> · <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: []
|