summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-03-05 11:10:34 -0700
committermo khan <mo@mokhan.ca>2025-03-05 11:10:34 -0700
commit502228f90f6e3e7b03d2c3165a9b8b8f00e29dce (patch)
treef53507beb32ef880b9571826d989683f7ba2ee59
parent98512feda282c2138e0bad54eb491f00e1cd5105 (diff)
feat: add a REST API service
-rwxr-xr-xbin/idp2
-rwxr-xr-xbin/rest-api106
-rwxr-xr-xbin/sp2
-rw-r--r--magefile.go7
4 files changed, 114 insertions, 3 deletions
diff --git a/bin/idp b/bin/idp
index fc276bb..d568c60 100755
--- a/bin/idp
+++ b/bin/idp
@@ -320,5 +320,5 @@ if __FILE__ == $0
run IdentityProvider.new
end.to_app
- Rackup::Server.start(app: app, Port: 8282)
+ Rackup::Server.start(app: app, Port: ENV.fetch('PORT', 8282).to_i)
end
diff --git a/bin/rest-api b/bin/rest-api
new file mode 100755
index 0000000..ca0a891
--- /dev/null
+++ b/bin/rest-api
@@ -0,0 +1,106 @@
+#!/usr/bin/env ruby
+
+require 'bundler/inline'
+
+gemfile do
+ source 'https://rubygems.org'
+
+ gem "erb", "~> 4.0"
+ gem "json", "~> 2.0"
+ gem "rack", "~> 3.0"
+ gem "rackup", "~> 2.0"
+ gem "securerandom", "~> 0.1"
+ gem "webrick", "~> 1.0"
+end
+
+class Project
+ class << self
+ def all
+ @projects ||= []
+ end
+
+ def create!(attributes)
+ new({ id: SecureRandom.uuid }.merge(attributes)).tap do |item|
+ all << item
+ end
+ end
+ end
+
+ def initialize(attributes = {})
+ @attributes = attributes
+ end
+
+ def to_h
+ @attributes
+ end
+end
+
+class RESTAPI
+ def initialize
+ @storage = {}
+ end
+
+ def call(env)
+ request = Rack::Request.new(env)
+ path = env['PATH_INFO']
+ case env['REQUEST_METHOD']
+ when 'GET'
+ case path
+ when '/projects.json'
+ return json_ok(Project.all.map(&:to_h))
+ else
+ return json_not_found
+ end
+ when 'POST'
+ case path
+ when "/projects"
+ if authorized?(request, :create_project)
+ return json_created(Project.create!(JSON.parse(request.body.read, symbolize_names: true)))
+ else
+ return json_unauthorized(:create_project)
+ end
+ else
+ return json_not_found
+ end
+ end
+ json_not_found
+ end
+
+ private
+
+ def authorized?(request, permission)
+ # TODO:: Check the JWT for the appropriate claim
+ # Connect to the Authz RPC endpoint Ability.allowed?(subject, permission, resource)
+ true
+ end
+
+ def json_not_found
+ [404, { 'X-Backend-Server' => 'REST', 'Content-Type' => 'application/json' }, []]
+ end
+
+ def json_ok(body)
+ [200, { 'Content-Type' => 'application/json' }, [JSON.pretty_generate(body)]]
+ end
+
+ def json_created(body)
+ [201, { 'Content-Type' => 'application/json' }, [JSON.pretty_generate(body.to_h)]]
+ end
+
+ def json_unauthorized(permission)
+ [401, { 'Content-Type' => 'application/json' }, [JSON.pretty_generate({
+ error: {
+ code: 401,
+ message: "`#{permission}` is required",
+ }
+ })]]
+ end
+end
+
+if __FILE__ == $0
+ app = Rack::Builder.new do
+ use Rack::Reloader
+ run RESTAPI.new
+ end.to_app
+
+ Rackup::Server.start(app: app, Port: ENV.fetch('PORT', 8284).to_i)
+end
diff --git a/bin/sp b/bin/sp
index 8fe7472..f570dab 100755
--- a/bin/sp
+++ b/bin/sp
@@ -159,5 +159,5 @@ if __FILE__ == $0
run ServiceProvider.new
end.to_app
- Rackup::Server.start(app: app, Port: 8283)
+ Rackup::Server.start(app: app, Port: ENV.fetch('PORT', 8283).to_i)
end
diff --git a/magefile.go b/magefile.go
index 39afbf9..40df5ad 100644
--- a/magefile.go
+++ b/magefile.go
@@ -30,6 +30,11 @@ func RunGateway() error {
return sh.RunV("go", "run", "./cmd/gtwy/main.go")
}
+// Run the REST API
+func RunApi() error {
+ return sh.RunV("ruby", "./bin/rest-api")
+}
+
// Open a web browser to the login page
func Browser() error {
if runtime.GOOS == "linux" {
@@ -41,5 +46,5 @@ func Browser() error {
// Run All the servers
func Run(ctx context.Context) {
- mg.CtxDeps(ctx, RunIdp, RunSp, RunGateway, Browser)
+ mg.CtxDeps(ctx, RunIdp, RunSp, RunApi, RunGateway)
}