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

Full OAuth & OIDC compliant API #55

Merged
merged 22 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,7 @@ gem "standard", "~> 1.33"
gem "standard-rails", "~> 1.0"

gem "syntax_suggest", "~> 2.0"

gem "doorkeeper", "~> 5.6"

gem "doorkeeper-openid_connect", "~> 1.8"
8 changes: 8 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 @@ -178,6 +183,7 @@ GEM
marcel (1.0.2)
matrix (0.4.2)
mini_mime (1.1.5)
mini_portile2 (2.8.5)
minitest (5.21.2)
msgpack (1.7.2)
multi_xml (0.6.0)
Expand Down Expand Up @@ -445,6 +451,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 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).domain_created_email.deliver_later
elsif params[:provisional_action] == "reject"
app.destroy!
end
end

private

def require_admin
Expand Down
18 changes: 18 additions & 0 deletions app/controllers/api/v1/api_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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::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
Loading