From 44a0ee1bf7cf1aff6c5bfc3ea6a437c49b5fb4e0 Mon Sep 17 00:00:00 2001 From: mo khan Date: Wed, 12 Mar 2025 11:52:07 -0600 Subject: feat: render a simple login page --- bin/idp | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 72 insertions(+), 15 deletions(-) (limited to 'bin/idp') diff --git a/bin/idp b/bin/idp index 04d1348..f90dd28 100755 --- a/bin/idp +++ b/bin/idp @@ -10,6 +10,7 @@ gemfile do gem "globalid", "~> 1.0" gem "google-protobuf", "~> 3.0" gem "rack", "~> 3.0" + gem "rack-session", "~> 2.0" gem "rackup", "~> 2.0" gem "saml-kit", "~> 1.0" gem "twirp", "~> 1.0" @@ -28,7 +29,7 @@ $host = ENV.fetch("HOST", "localhost:#{$port}") module HTTPHelpers def default_headers { - 'X-Powered-By' => 'IDP' + 'X-Powered-By' => 'IdP' } end @@ -43,6 +44,18 @@ end module Authn class User + class << self + def find_by_username(username) + User.new(id: SecureRandom.uuid, username: username, email: "#{username}@example.com") + end + + def find_by_credentials(params = {}) + user = find_by_username(params["username"]) + return user if user.valid_password?(params["password"]) + + end + end + def initialize(attributes) @attributes = attributes end @@ -55,12 +68,20 @@ module Authn end end + def create_access_token + ::Authz::JWT.new(sub: @attributes[:id], iat: Time.now.to_i) + end + def assertion_attributes_for(request) { email: @attributes[:email], - access_token: ::Authz::JWT.new(sub: SecureRandom.uuid, iat: Time.now.to_i).to_jwt, } end + + def valid_password?(entered_password) + # TODO:: BCrypt hash secure compare + true + end end class OnDemandRegistry < Saml::Kit::DefaultRegistry @@ -91,7 +112,6 @@ module Authn x.add_single_sign_on_service("#{scheme}://#{host}/saml/new", binding: :http_post) x.name_id_formats = [Saml::Kit::Namespaces::PERSISTENT, Saml::Kit::Namespaces::EMAIL_ADDRESS] x.attributes << :email - x.attributes << :access_token end end end @@ -106,14 +126,18 @@ module Authn { 'Content-Type' => "application/samlmetadata+xml" }, saml_metadata.to_xml(pretty: true) ) - when "/new" - # TODO:: render a login page - return saml_post_back(Rack::Request.new(env)) end when 'POST' case path when "/new" - return saml_post_back(Rack::Request.new(env)) + return login_page(Rack::Request.new(env)) + when "/login" + request = Rack::Request.new(env) + if (user = User.find_by_credentials(request.params)) + return saml_post_back(request, user) + else + return login_page(request) + end end end @@ -124,20 +148,48 @@ module Authn attr_reader :saml_metadata - def saml_post_back(request) + def login_page(request) + saml_params = saml_params_from(request) + saml_request = binding_for(request).deserialize(saml_params) + raise saml_request.errors unless saml_request.valid? + + template = <<~ERB + + + + +

Recieved SAML Request

+ + +
+ + + <%- saml_params.each do |(key, value)| -%> + + <%- end -%> + +
+ + + ERB + erb = ERB.new(template, trim_mode: '-') + html = erb.result(binding) + [200, { 'Content-Type' => "text/html" }, [html]] + end + + def saml_post_back(request, user) params = saml_params_from(request) saml_request = binding_for(request).deserialize(params) + request.session[:access_token] = user.create_access_token + @builder = nil url, saml_params = saml_request.response_for( - User.new({ id: SecureRandom.uuid, email: "example@example.com" }), + user, binding: :http_post, relay_state: params[:RelayState] - ) do |builder| - builder.embed_signature = true - @builder = builder - end + ) { |builder| @builder = builder } template = <<~ERB - + @@ -274,6 +326,9 @@ module Authz when "/authorize" # RFC-6749 return post_authorize(Rack::Request.new(env)) when "/token" # RFC-6749 + request = Rack::Request.new(env) + request.body + return [200, { 'Content-Type' => "application/json" }, [JSON.pretty_generate({ access_token: ::Authz::JWT.new(sub: SecureRandom.uuid, iat: Time.now.to_i).to_jwt, token_type: "Bearer", @@ -292,7 +347,7 @@ module Authz def get_authorize(request) template = <<~ERB - + @@ -449,6 +504,8 @@ if __FILE__ == $0 app = Rack::Builder.new do use Rack::CommonLogger use Rack::Reloader + use Rack::Session::Cookie, { domain: $host.split(":", 2)[0], path: "/", secret: SecureRandom.hex(64) } + map "/twirp" do # https://github.com/arthurnn/twirp-ruby/wiki/Service-Handlers run ::Authx::Rpc::AbilityService.new(::Authz::Rpc::Ability.new) -- cgit v1.2.3