Skip to content
This repository has been archived by the owner on May 2, 2024. It is now read-only.

Commit

Permalink
Full OAuth & OIDC compliant API (#55)
Browse files Browse the repository at this point in the history
Co-authored-by: Caleb Denio <[email protected]>
Co-authored-by: Samuel Fernandez <[email protected]>
  • Loading branch information
3 people authored Jan 26, 2024
1 parent ecfe247 commit 011e5e9
Show file tree
Hide file tree
Showing 75 changed files with 2,613 additions and 45 deletions.
4 changes: 4 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"tabWidth": 2,
"useTabs": false
}
6 changes: 6 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,9 @@ gem "standard", "~> 1.33"
gem "standard-rails", "~> 1.0"

gem "syntax_suggest", "~> 2.0"

gem "doorkeeper", "~> 5.6"

gem "doorkeeper-openid_connect", "~> 1.8"

gem "rack-cors", "~> 2.0"
10 changes: 10 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ GEM
diff-lcs (1.5.0)
dnsimple (8.7.1)
httparty
doorkeeper (5.6.8)
railties (>= 5)
doorkeeper-openid_connect (1.8.7)
doorkeeper (>= 5.5, < 5.7)
jwt (>= 2.5)
dotenv (2.8.1)
dotenv-rails (2.8.1)
dotenv (= 2.8.1)
Expand Down Expand Up @@ -224,6 +229,8 @@ GEM
nio4r (~> 2.0)
racc (1.7.3)
rack (3.0.8)
rack-cors (2.0.1)
rack (>= 2.0.0)
rack-session (2.0.0)
rack (>= 3.0.0)
rack-test (2.1.0)
Expand Down Expand Up @@ -445,6 +452,8 @@ DEPENDENCIES
dalli (~> 3.2)
debug
dnsimple (~> 8.1)
doorkeeper (~> 5.6)
doorkeeper-openid_connect (~> 1.8)
dotenv-rails
erb-formatter
importmap-rails (~> 1.2)
Expand All @@ -453,6 +462,7 @@ DEPENDENCIES
postmark-rails
propshaft
puma (~> 6.0)
rack-cors (~> 2.0)
rails (~> 7.1.2)
rails_hotreload
rails_nestable_layouts!
Expand Down
Binary file added app/assets/images/oblong-dev.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 63 additions & 3 deletions app/assets/stylesheets/application.tailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@
.side-menu > * > span {
@apply font-heading text-3;
}

.side-menu > a {
@apply font-heading text-3;
}
.side-menu > * > svg {
@apply fill-pink w-10 h-10 xl:w-12 xl:h-12;
}
Expand All @@ -208,7 +212,7 @@
@apply text-yellow font-heading text-1 md:text-2 xl:text-3 text-left;
}
.table > tbody {
@apply bg-[#444343];
@apply bg-[#262626];
}
.table td {
@apply border-collapse border-y text-1 md:text-2 px-6 py-3 md:py-4;
Expand Down Expand Up @@ -269,14 +273,18 @@ button, input[type=submit] {
min-width: min-content;
}

input[type=text], input[type=email], input[type=number] {
background-color: rgba(245, 245, 245, 0.1) !important;
input[type=text], input[type=email], input[type=number], .input2, x-selectmenu::part(button) {
background-color: #262626 !important;
border: 1.5px solid var(--cultured)!important;
border-radius: 5px !important;
min-width: 350px;
color: white;
}

.admin > input[type=text], input[type=email], input[type=number], .input2, x-selectmenu::part(button) {
background-color: rgba(245, 245, 245, 0.1) !important;
}

input[type=number] {
min-width: 100px !important;
width: 100px !important;
Expand Down Expand Up @@ -326,3 +334,55 @@ input[type=number] {
.outline-pink-border:hover {
background-color: var(--winter-sky) !important;
}

.card {
transition: ease-in-out 0.25s;
}

.card:hover {
transform: translateY(-1rem);
}


.clipbutton {
border-radius: 4px;
font-size: 1rem;
padding: 0.5rem;
background-color: var(--lemon-glacier);
color: #262626;
transition: ease-in-out 0.25s;
}

.clipbutton:hover {
transform: translateX(-4px);
}

.clipbutton.shaking {
animation: shake 0.82s cubic-bezier(.36,.07,.19,.97) both;
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
perspective: 1000px;
animation-fill-mode: forwards;
}

@keyframes shake {
10%, 90% {
transform: translate3d(-1px, 0, 0);
}

20%, 80% {
transform: translate3d(2px, 0, 0);
}

30%, 50%, 70% {
transform: translate3d(-4px, 0, 0);
}

40%, 60% {
transform: translate3d(4px, 0, 0);
}
}

[behavior="selected-value"] > .description {
display: none;
}
17 changes: 16 additions & 1 deletion app/controllers/admin_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ def domains
end

def review
@domains = Domain.where(provisional: true).map { |d| {domain_id: d.id, host: d.host, plan: d.plan} }
@domains = Domain.where(provisional: true).map { |d| {resource_id: d.id, name: d.host, plan: d.plan} }
end

def developers_review
@apps = Doorkeeper::Application.where(provisional: true).map { |d| {resource_id: d.id, name: d.name, plan: d.plan, uri: d.redirect_uri} }
end

def review_decision
Expand All @@ -32,6 +36,17 @@ def review_decision
end
end

def developers_review_decision
app = Doorkeeper::Application.find_by(id: params[:application_id])

if params[:provisional_action] == "accept"
app.update!(provisional: false)
Developers::ApplicationMailer.with(email: User::User.find_by(id: app.owner_id).email, app: app.name).app_created_email.deliver_later
elsif params[:provisional_action] == "reject"
app.destroy!
end
end

private

def require_admin
Expand Down
21 changes: 21 additions & 0 deletions app/controllers/api/v1/api_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class Api::V1::ApiController < ActionController::Base #standard:disable all
skip_before_action :verify_authenticity_token

before_action :not_provisional

private

# Find the user that owns the access token
def current_user
User::User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
end

def not_provisional
if doorkeeper_token.nil?
return
end
if Doorkeeper::Application.find_by(id: doorkeeper_token.application_id).provisional?
render plain: "425 Too Early - Provisional Client", status: 425
end
end
end
47 changes: 47 additions & 0 deletions app/controllers/api/v1/domains/records_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
class Api::V1::Domains::RecordsController < Api::V1::ApiController
include DomainAuthorization
before_action do
doorkeeper_authorize! :domains
doorkeeper_authorize! :domains_records
end

before_action only: [:create, :update, :destroy] do
doorkeeper_authorize! :domains_records_write
end

def index
@records = current_domain.records
end

def create
@record = Record.create(domain_id: current_domain.id, name: params["name"], type: params["type"], content: params["content"], ttl: params["ttl"], priority: params["priority"]) # standard:disable all
render "show"
end

def show
@record = Record.find(params[:id])
end

def update
@record = Record.find(params[:id])

(@record.type = params[:type]) if params[:type]
(@record.name = params[:name]) if params[:name]
(@record.content = params[:content]) if params[:content]
(@record.ttl = params[:ttl]) if params[:ttl]
(@record.priority = params[:priority]) if params[:priority]

@record.save # standard:disable all

render "show"
end

def destroy
@record = Record.find(params[:id])

@record.destroy! #standard:disable all

index
render "index"
end
end
43 changes: 43 additions & 0 deletions app/controllers/api/v1/domains_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
class Api::V1::DomainsController < Api::V1::ApiController
include DomainAuthorization
before_action do
doorkeeper_authorize! :domains
end

before_action only: [:create, :destroy] do
doorkeeper_authorize! :domains_write
end

skip_before_action :authorize_domain, only: [:index, :create]

def index
@domains = Domain.where(user_users_id: current_user.id)
if params[:records]
doorkeeper_authorize!(:domains_records)
end
end

def show
@domain = Domain.find_by(host: params[:host])
if params[:records]
doorkeeper_authorize!(:domains_records)
end
end

def create
@domain = Domain.new(host: params[:host], plan: params[:plan], provisional: true, user_users_id: current_user.id)
if @domain.save
render "show"
else
render json: @domain.errors, status: 418
end
end

def destroy
@domain = Domain.find_by(host: params[:host])
@domain.destroy!

index
render "index"
end
end
49 changes: 49 additions & 0 deletions app/controllers/api/v1/user_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
class Api::V1::UserController < Api::V1::ApiController
before_action do
doorkeeper_authorize! :user
end

def show
if doorkeeper_token.scopes.exists?(:name)
@name = current_user.name
end

if doorkeeper_token.scopes.exists?(:email)
@email = current_user.email
end

@id = current_user.id
@created_at = current_user.created_at
@updated_at = current_user.updated_at
@verified = current_user.verified

if doorkeeper_token.scopes.exists?(:admin)
@admin = current_user.admin
end
end

def update
redirected = false

if params[:name]
if doorkeeper_authorize! :name_write
redirected = true
else
current_user.update!(name: params[:name])
end
end

if params[:email]
if doorkeeper_authorize! :email_write
redirected = true
else
current_user.update!(email: params[:email])
end
end

if !redirected
show
render "show"
end
end
end
4 changes: 2 additions & 2 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ def current_user

def check_auth
if session[:authenticated] != true
redirect_to controller: "auth", action: "login"
redirect_to controller: "/auth", action: "login"
end
end

def check_verified
if !session[:authenticated]
check_auth
elsif !current_user.verified?
redirect_to controller: "users", action: "email_verification", params: {skip_passkey: true}
redirect_to controller: "/users", action: "email_verification", params: {skip_passkey: true}
end
end
end
4 changes: 2 additions & 2 deletions app/controllers/auth_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def verify_code
session[:authenticated] = true
session[:current_user_id] = u.id

redirect_to(root_path, notice: (User::Credential.where(user_users_id: u.id).length == 0) ? "Passkeys are more secure & convienient way to login. Head to Account Settings to add one." : "To disable insecure email code authentication, head to Account Settings.")
redirect_to(session[:return_path] || root_path, notice: (User::Credential.where(user_users_id: u.id).length == 0) ? "Passkeys are more secure & convienient way to login. Head to Account Settings to add one." : "To disable insecure email code authentication, head to Account Settings.")
else
render inline: "<%= turbo_stream.replace \"error\" do %><p class=\"error\">Invalid OTP</p><% end %>", status: :unprocessable_entity, format: :turbo_stream
end
Expand All @@ -57,7 +57,7 @@ def create_key
user.verified = true
user.save!
session[:authenticated] = true
redirect_to(root_path, notice: "To add a passkey in the future, head to Account Settings")
redirect_to(session[:return_path] || root_path, notice: "To add a passkey in the future, head to Account Settings")
end

@options = WebAuthn::Credential.options_for_create(
Expand Down
Loading

0 comments on commit 011e5e9

Please sign in to comment.