-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
169 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,4 +5,4 @@ require "faker" | |
require "../src/authly" | ||
require "./support/settings" | ||
|
||
BASE_URI = "http://0.0.0.0:4000" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
require "http/server" | ||
require "../src/authly" | ||
require "../../src/authly" | ||
require "./settings" | ||
|
||
server = HTTP::Server.new([ | ||
Authly::OAuthHandler.new, | ||
]) | ||
server.bind_tcp "0.0.0.0", 4000 | ||
puts "Listening on http://0.0.0.0:4000" | ||
|
||
server.listen |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
require "http/server/handler" | ||
require "URI" | ||
|
||
module Authly | ||
class AuthorizationHandler | ||
STATE_STORE = Authly.config.state_store | ||
|
||
def self.handle(context) | ||
params = context.request.query_params | ||
client_id = params.fetch("client_id", "") | ||
redirect_uri = params.fetch("redirect_uri", "") | ||
response_type = params.fetch("response_type", "") | ||
scope = params.fetch("scope", "") | ||
code_challenge = params.fetch("code_challenge", "") | ||
challenge_method = params.fetch("code_challenge_method", "") | ||
user_id = params.fetch("user_id", "") | ||
state = params.fetch("state", "") | ||
|
||
# Store the state parameter to verify later | ||
STATE_STORE.store(state) | ||
|
||
# Check if user has given consent | ||
unless user_has_given_consent?(context) | ||
# Render consent page where the user can approve or deny the requested access | ||
render_consent_page(context, client_id, scope, state) | ||
return | ||
end | ||
|
||
# Verify the state parameter to prevent CSRF attacks | ||
unless STATE_STORE.valid?(state) | ||
ResponseHelper.write(context, 400, "text/plain", "Invalid state parameter") | ||
return | ||
end | ||
|
||
|
||
# Generate authorization code after user consent | ||
authorization_code = Authly.code( | ||
response_type, | ||
client_id, | ||
redirect_uri, | ||
scope, | ||
code_challenge, | ||
challenge_method, | ||
user_id | ||
).to_s | ||
|
||
# Redirect the user-agent back to the redirect_uri with the code and state | ||
redirect_location = URI.parse(redirect_uri) | ||
redirect_location.query = URI.encode_path("code=#{authorization_code}&state=#{state}") | ||
ResponseHelper.redirect(context, redirect_location.to_s) | ||
rescue e : Error | ||
ResponseHelper.write(context, e.code, "text/plain", e.message) | ||
end | ||
|
||
private def self.user_has_given_consent?(context) | ||
# Logic to determine if the user has already given consent | ||
# This can be done by checking session or context information | ||
consent = context.request.query_params["consent"]? | ||
consent == "approved" | ||
end | ||
|
||
private def self.render_consent_page(context, client_id, scope, state = SecureRandom.hex(32)) | ||
# Render a simple consent page where the user can approve or deny the requested access | ||
ResponseHelper.write(context, 200, "text/html", <<-HTML) | ||
<html> | ||
<body> | ||
<h1>Authorization Request</h1> | ||
<p>Client ID: #{client_id}</p> | ||
<p>Requested Scopes: #{scope}</p> | ||
<form action="/authorize" method="get"> | ||
<input type="hidden" name="client_id" value="#{client_id}"> | ||
<input type="hidden" name="scope" value="#{scope}"> | ||
<input type="hidden" name="state" value="#{state}"> | ||
<input type="hidden" name="redirect_uri" value="#{context.request.query_params["redirect_uri"]}"> | ||
<button type="submit" name="consent" value="approved">Approve</button> | ||
<button type="submit" name="consent" value="denied">Deny</button> | ||
</form> | ||
</body> | ||
</html> | ||
HTML | ||
end | ||
|
||
private def self.valid_state?(state) | ||
# Verify that the state parameter exists in the store | ||
STATE_STORE.valid?(state) == true | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
module Authly | ||
module ResponseHelper | ||
def self.write(context, status_code, content_type = "text/plain", body = "") | ||
context.response.status_code = status_code | ||
context.response.content_type = content_type | ||
context.response.write body.not_nil!.to_slice | ||
end | ||
|
||
def self.redirect(context, location) | ||
context.response.status_code = 302 | ||
context.response.headers["Location"] = location | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
module Authly | ||
module StateStore | ||
abstract def store(state : String) | ||
abstract def valid?(state : String) : Bool | ||
end | ||
|
||
class InMemoryStateStore | ||
include StateStore | ||
|
||
@store : Hash(String, Bool) | ||
|
||
def initialize | ||
@store = Hash(String, Bool).new | ||
end | ||
|
||
def store(state : String) | ||
@store[state] = true | ||
end | ||
|
||
def valid?(state : String) : Bool | ||
@store[state] == true | ||
end | ||
end | ||
end |