mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 01:01:39 -05:00
Implement OIDC authentication for Dawarich using omniauth_openid_connect gem.
This commit is contained in:
parent
48e50c2ee8
commit
e6c8bd30df
11 changed files with 552 additions and 5 deletions
|
|
@ -0,0 +1,50 @@
|
|||
# OAuth Configuration
|
||||
|
||||
# GitHub OAuth
|
||||
GITHUB_OAUTH_CLIENT_ID=
|
||||
GITHUB_OAUTH_CLIENT_SECRET=
|
||||
|
||||
# Google OAuth2
|
||||
GOOGLE_OAUTH_CLIENT_ID=
|
||||
GOOGLE_OAUTH_CLIENT_SECRET=
|
||||
|
||||
# Generic OpenID Connect (for Authelia, Authentik, Keycloak, etc.)
|
||||
# Option 1: Using OIDC Discovery (Recommended)
|
||||
# Set OIDC_ISSUER to your provider's issuer URL (e.g., https://auth.example.com)
|
||||
# The provider must support OpenID Connect Discovery (.well-known/openid-configuration)
|
||||
OIDC_CLIENT_ID=
|
||||
OIDC_CLIENT_SECRET=
|
||||
OIDC_ISSUER=
|
||||
OIDC_REDIRECT_URI=
|
||||
|
||||
# Option 2: Manual Endpoint Configuration (if discovery is not supported)
|
||||
# Use this if your provider doesn't support OIDC discovery
|
||||
# OIDC_CLIENT_ID=
|
||||
# OIDC_CLIENT_SECRET=
|
||||
# OIDC_HOST=auth.example.com
|
||||
# OIDC_SCHEME=https
|
||||
# OIDC_PORT=443
|
||||
# OIDC_AUTHORIZATION_ENDPOINT=/authorize
|
||||
# OIDC_TOKEN_ENDPOINT=/token
|
||||
# OIDC_USERINFO_ENDPOINT=/userinfo
|
||||
# OIDC_REDIRECT_URI=https://yourdomain.com/users/auth/openid_connect/callback
|
||||
|
||||
# Example configurations:
|
||||
#
|
||||
# Authelia:
|
||||
# OIDC_ISSUER=https://auth.example.com
|
||||
# OIDC_CLIENT_ID=your-client-id
|
||||
# OIDC_CLIENT_SECRET=your-client-secret
|
||||
# OIDC_REDIRECT_URI=https://dawarich.example.com/users/auth/openid_connect/callback
|
||||
#
|
||||
# Authentik:
|
||||
# OIDC_ISSUER=https://authentik.example.com/application/o/dawarich/
|
||||
# OIDC_CLIENT_ID=your-client-id
|
||||
# OIDC_CLIENT_SECRET=your-client-secret
|
||||
# OIDC_REDIRECT_URI=https://dawarich.example.com/users/auth/openid_connect/callback
|
||||
#
|
||||
# Keycloak:
|
||||
# OIDC_ISSUER=https://keycloak.example.com/realms/your-realm
|
||||
# OIDC_CLIENT_ID=dawarich
|
||||
# OIDC_CLIENT_SECRET=your-client-secret
|
||||
# OIDC_REDIRECT_URI=https://dawarich.example.com/users/auth/openid_connect/callback
|
||||
|
|
@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
# [UNRELEASED]
|
||||
|
||||
- Implemented authentication via GitHub and Google for Dawarich Cloud.
|
||||
- Implemented OpenID Connect authentication for self-hosted Dawarich instances. #66
|
||||
|
||||
# [0.34.0] - 2025-10-10
|
||||
|
||||
## The Family release
|
||||
|
|
|
|||
1
Gemfile
1
Gemfile
|
|
@ -26,6 +26,7 @@ gem 'lograge'
|
|||
gem 'oj'
|
||||
gem 'omniauth-github', '~> 2.0.0'
|
||||
gem 'omniauth-google-oauth2'
|
||||
gem 'omniauth_openid_connect'
|
||||
gem 'omniauth-rails_csrf_protection'
|
||||
gem 'parallel'
|
||||
gem 'pg'
|
||||
|
|
|
|||
50
Gemfile.lock
50
Gemfile.lock
|
|
@ -86,8 +86,10 @@ GEM
|
|||
uri (>= 0.13.1)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
aes_key_wrap (1.1.0)
|
||||
ast (2.4.3)
|
||||
attr_extras (7.1.0)
|
||||
attr_required (1.0.2)
|
||||
aws-eventstream (1.3.2)
|
||||
aws-partitions (1.1072.0)
|
||||
aws-sdk-core (3.215.1)
|
||||
|
|
@ -108,6 +110,7 @@ GEM
|
|||
bcrypt (3.1.20)
|
||||
benchmark (0.4.1)
|
||||
bigdecimal (3.2.3)
|
||||
bindata (2.5.1)
|
||||
bootsnap (1.18.6)
|
||||
msgpack (~> 1.2)
|
||||
brakeman (7.0.2)
|
||||
|
|
@ -161,6 +164,8 @@ GEM
|
|||
dotenv (= 3.1.8)
|
||||
railties (>= 6.1)
|
||||
drb (2.2.3)
|
||||
email_validator (2.2.4)
|
||||
activemodel
|
||||
erb (5.0.2)
|
||||
erubi (1.13.1)
|
||||
et-orbi (1.4.0)
|
||||
|
|
@ -175,6 +180,8 @@ GEM
|
|||
faraday-net_http (>= 2.0, < 3.5)
|
||||
json
|
||||
logger
|
||||
faraday-follow_redirects (0.4.0)
|
||||
faraday (>= 1, < 3)
|
||||
faraday-net_http (3.4.1)
|
||||
net-http (>= 0.5.0)
|
||||
ffaker (2.25.0)
|
||||
|
|
@ -220,6 +227,13 @@ GEM
|
|||
reline (>= 0.4.2)
|
||||
jmespath (1.6.2)
|
||||
json (2.15.0)
|
||||
json-jwt (1.17.0)
|
||||
activesupport (>= 4.2)
|
||||
aes_key_wrap
|
||||
base64
|
||||
bindata
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
json-schema (5.0.1)
|
||||
addressable (~> 2.8)
|
||||
jwt (2.10.1)
|
||||
|
|
@ -318,6 +332,22 @@ GEM
|
|||
omniauth-rails_csrf_protection (1.0.2)
|
||||
actionpack (>= 4.2)
|
||||
omniauth (~> 2.0)
|
||||
omniauth_openid_connect (0.8.0)
|
||||
omniauth (>= 1.9, < 3)
|
||||
openid_connect (~> 2.2)
|
||||
openid_connect (2.3.1)
|
||||
activemodel
|
||||
attr_required (>= 1.0.0)
|
||||
email_validator
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
json-jwt (>= 1.16)
|
||||
mail
|
||||
rack-oauth2 (~> 2.2)
|
||||
swd (~> 2.0)
|
||||
tzinfo
|
||||
validate_url
|
||||
webfinger (~> 2.0)
|
||||
optimist (3.2.1)
|
||||
orm_adapter (0.5.0)
|
||||
ostruct (0.6.1)
|
||||
|
|
@ -357,6 +387,13 @@ GEM
|
|||
raabro (1.4.0)
|
||||
racc (1.8.1)
|
||||
rack (3.2.2)
|
||||
rack-oauth2 (2.2.1)
|
||||
activesupport
|
||||
attr_required
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
json-jwt (>= 1.11.0)
|
||||
rack (>= 2.1.0)
|
||||
rack-protection (4.2.1)
|
||||
base64 (>= 0.1.0)
|
||||
logger (>= 1.6.0)
|
||||
|
|
@ -534,6 +571,11 @@ GEM
|
|||
attr_extras (>= 6.2.4)
|
||||
diff-lcs
|
||||
patience_diff
|
||||
swd (2.0.3)
|
||||
activesupport (>= 3)
|
||||
attr_required (>= 0.0.5)
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
tailwindcss-rails (3.3.2)
|
||||
railties (>= 7.0.0)
|
||||
tailwindcss-ruby (~> 3.0)
|
||||
|
|
@ -557,9 +599,16 @@ GEM
|
|||
unicode-emoji (4.1.0)
|
||||
uri (1.0.3)
|
||||
useragent (0.16.11)
|
||||
validate_url (1.0.15)
|
||||
activemodel (>= 3.0.0)
|
||||
public_suffix
|
||||
version_gem (1.1.9)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
webfinger (2.1.3)
|
||||
activesupport
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
webmock (3.25.1)
|
||||
addressable (>= 2.8.0)
|
||||
crack (>= 0.3.2)
|
||||
|
|
@ -614,6 +663,7 @@ DEPENDENCIES
|
|||
omniauth-github (~> 2.0.0)
|
||||
omniauth-google-oauth2
|
||||
omniauth-rails_csrf_protection
|
||||
omniauth_openid_connect
|
||||
parallel
|
||||
pg
|
||||
prometheus_exporter
|
||||
|
|
|
|||
|
|
@ -126,6 +126,14 @@ Feel free to change them in the account settings.
|
|||
- Provide credentials for Immich or Photoprism (or both!) and Dawarich will automatically import geodata from your photos.
|
||||
- You'll also be able to visualize your photos on the map!
|
||||
|
||||
### 🔐 Authentication
|
||||
- Multiple authentication options:
|
||||
- Email/Password (built-in)
|
||||
- GitHub OAuth
|
||||
- Google OAuth2
|
||||
- OpenID Connect (OIDC) - works with Authelia, Authentik, Keycloak, and other OIDC providers
|
||||
- See [OIDC Setup Guide](OIDC_SETUP.md) for detailed configuration instructions
|
||||
|
||||
### 📥 Import Your Data
|
||||
- Import from various sources:
|
||||
- Google Maps Timeline
|
||||
|
|
|
|||
|
|
@ -9,6 +9,36 @@ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||
handle_auth('Google')
|
||||
end
|
||||
|
||||
def openid_connect
|
||||
handle_auth('OpenID Connect')
|
||||
end
|
||||
|
||||
def failure
|
||||
error_type = request.env['omniauth.error.type']
|
||||
error = request.env['omniauth.error']
|
||||
|
||||
# Provide user-friendly error messages
|
||||
error_message =
|
||||
case error_type
|
||||
when :invalid_credentials
|
||||
'Invalid credentials. Please check your username and password.'
|
||||
when :timeout
|
||||
'Connection timeout. Please try again.'
|
||||
when :csrf_detected
|
||||
'Security error detected. Please try again.'
|
||||
else
|
||||
if error&.message&.include?('Discovery')
|
||||
'Unable to connect to authentication provider. Please contact your administrator.'
|
||||
elsif error&.message&.include?('Issuer mismatch')
|
||||
'Authentication provider configuration error. Please contact your administrator.'
|
||||
else
|
||||
"Authentication failed: #{params[:message] || error&.message || 'Unknown error'}"
|
||||
end
|
||||
end
|
||||
|
||||
redirect_to root_path, alert: error_message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_auth(provider)
|
||||
|
|
@ -21,8 +51,4 @@ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||
redirect_to new_user_registration_url, alert: @user.errors.full_messages.join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
def failure
|
||||
redirect_to root_path, alert: "Authentication failed: #{params[:message]}"
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ class User < ApplicationRecord # rubocop:disable Metrics/ClassLength
|
|||
include UserFamily
|
||||
devise :database_authenticatable, :registerable,
|
||||
:recoverable, :rememberable, :validatable, :trackable,
|
||||
:omniauthable, omniauth_providers: %i[github google_oauth2]
|
||||
:omniauthable, omniauth_providers: %i[github google_oauth2 openid_connect]
|
||||
|
||||
has_many :points, dependent: :destroy
|
||||
has_many :imports, dependent: :destroy
|
||||
|
|
|
|||
|
|
@ -269,6 +269,48 @@ Devise.setup do |config|
|
|||
config.omniauth :google_oauth2, ENV['GOOGLE_OAUTH_CLIENT_ID'], ENV['GOOGLE_OAUTH_CLIENT_SECRET'],
|
||||
scope: 'userinfo.email,userinfo.profile'
|
||||
|
||||
# Generic OpenID Connect provider (Authelia, Authentik, Keycloak, etc.)
|
||||
# Supports both discovery mode (preferred) and manual endpoint configuration
|
||||
if ENV['OIDC_CLIENT_ID'].present? && ENV['OIDC_CLIENT_SECRET'].present?
|
||||
oidc_config = {
|
||||
name: :openid_connect,
|
||||
scope: %i[openid email profile],
|
||||
response_type: :code,
|
||||
client_options: {
|
||||
identifier: ENV['OIDC_CLIENT_ID'],
|
||||
secret: ENV['OIDC_CLIENT_SECRET'],
|
||||
redirect_uri: ENV.fetch('OIDC_REDIRECT_URI', "#{ENV.fetch('APPLICATION_URL', 'http://localhost:3000')}/users/auth/openid_connect/callback")
|
||||
}
|
||||
}
|
||||
|
||||
# Use OIDC discovery if issuer is provided (recommended for Authelia, Authentik, Keycloak)
|
||||
if ENV['OIDC_ISSUER'].present?
|
||||
oidc_config[:issuer] = ENV['OIDC_ISSUER']
|
||||
oidc_config[:discovery] = true
|
||||
Rails.logger.info "OIDC: Discovery mode enabled with issuer: #{ENV['OIDC_ISSUER']}"
|
||||
# Otherwise use manual endpoint configuration
|
||||
elsif ENV['OIDC_HOST'].present?
|
||||
oidc_config[:client_options].merge!(
|
||||
{
|
||||
host: ENV['OIDC_HOST'],
|
||||
scheme: ENV.fetch('OIDC_SCHEME', 'https'),
|
||||
port: ENV.fetch('OIDC_PORT', 443).to_i,
|
||||
authorization_endpoint: ENV.fetch('OIDC_AUTHORIZATION_ENDPOINT', '/authorize'),
|
||||
token_endpoint: ENV.fetch('OIDC_TOKEN_ENDPOINT', '/token'),
|
||||
userinfo_endpoint: ENV.fetch('OIDC_USERINFO_ENDPOINT', '/userinfo')
|
||||
}
|
||||
)
|
||||
Rails.logger.info "OIDC: Manual mode enabled with host: #{ENV['OIDC_SCHEME']}://#{ENV['OIDC_HOST']}:#{ENV.fetch(
|
||||
'OIDC_PORT', 443
|
||||
)}"
|
||||
end
|
||||
|
||||
Rails.logger.info "OIDC: Client ID: #{ENV['OIDC_CLIENT_ID']}, Redirect URI: #{oidc_config[:client_options][:redirect_uri]}"
|
||||
config.omniauth :openid_connect, oidc_config
|
||||
else
|
||||
Rails.logger.warn 'OIDC: Not configured (missing OIDC_CLIENT_ID or OIDC_CLIENT_SECRET)'
|
||||
end
|
||||
|
||||
# ==> Warden configuration
|
||||
# If you want to use other strategies, that are not supported by Devise, or
|
||||
# change the failure app, you can configure them inside the config.warden block.
|
||||
|
|
|
|||
|
|
@ -336,4 +336,97 @@ RSpec.describe User, type: :model do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.from_omniauth' do
|
||||
let(:auth_hash) do
|
||||
OmniAuth::AuthHash.new({
|
||||
provider: 'github',
|
||||
uid: '123545',
|
||||
info: {
|
||||
email: email,
|
||||
name: 'Test User'
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
context 'when user exists with the same email' do
|
||||
let(:email) { 'existing@example.com' }
|
||||
let!(:existing_user) { create(:user, email: email) }
|
||||
|
||||
it 'returns the existing user' do
|
||||
user = described_class.from_omniauth(auth_hash)
|
||||
expect(user).to eq(existing_user)
|
||||
expect(user.persisted?).to be true
|
||||
end
|
||||
|
||||
it 'does not create a new user' do
|
||||
expect do
|
||||
described_class.from_omniauth(auth_hash)
|
||||
end.not_to change(User, :count)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user does not exist' do
|
||||
let(:email) { 'new@example.com' }
|
||||
|
||||
it 'creates a new user with the OAuth email' do
|
||||
expect do
|
||||
described_class.from_omniauth(auth_hash)
|
||||
end.to change(User, :count).by(1)
|
||||
|
||||
user = User.last
|
||||
expect(user.email).to eq(email)
|
||||
end
|
||||
|
||||
it 'generates a random password for the new user' do
|
||||
user = described_class.from_omniauth(auth_hash)
|
||||
expect(user.encrypted_password).to be_present
|
||||
end
|
||||
|
||||
it 'returns a persisted user' do
|
||||
user = described_class.from_omniauth(auth_hash)
|
||||
expect(user.persisted?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when OAuth provider is Google' do
|
||||
let(:email) { 'google@example.com' }
|
||||
let(:auth_hash) do
|
||||
OmniAuth::AuthHash.new({
|
||||
provider: 'google_oauth2',
|
||||
uid: '123545',
|
||||
info: {
|
||||
email: email,
|
||||
name: 'Google User'
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
it 'creates a user from Google OAuth data' do
|
||||
user = described_class.from_omniauth(auth_hash)
|
||||
expect(user.email).to eq(email)
|
||||
expect(user.persisted?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when email is nil' do
|
||||
let(:email) { nil }
|
||||
|
||||
it 'attempts to create a user but fails validation' do
|
||||
user = described_class.from_omniauth(auth_hash)
|
||||
expect(user.persisted?).to be false
|
||||
expect(user.errors[:email]).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'when email is blank' do
|
||||
let(:email) { '' }
|
||||
|
||||
it 'attempts to create a user but fails validation' do
|
||||
user = described_class.from_omniauth(auth_hash)
|
||||
expect(user.persisted?).to be false
|
||||
expect(user.errors[:email]).to be_present
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
167
spec/requests/users/omniauth_callbacks_spec.rb
Normal file
167
spec/requests/users/omniauth_callbacks_spec.rb
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Users::OmniauthCallbacks', type: :request do
|
||||
let(:email) { 'oauth_user@example.com' }
|
||||
|
||||
before do
|
||||
Rails.application.env_config['devise.mapping'] = Devise.mappings[:user]
|
||||
end
|
||||
|
||||
shared_examples 'successful OAuth authentication' do |provider, provider_name|
|
||||
context "when user doesn't exist" do
|
||||
it 'creates a new user and signs them in' do
|
||||
expect do
|
||||
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[provider]
|
||||
get "/users/auth/#{provider}/callback"
|
||||
end.to change(User, :count).by(1)
|
||||
|
||||
expect(response).to redirect_to(root_path)
|
||||
|
||||
user = User.find_by(email: email)
|
||||
expect(user).to be_present
|
||||
expect(user.encrypted_password).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user already exists' do
|
||||
let!(:existing_user) { create(:user, email: email) }
|
||||
|
||||
it 'signs in the existing user without creating a new one' do
|
||||
expect do
|
||||
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[provider]
|
||||
get "/users/auth/#{provider}/callback"
|
||||
end.not_to change(User, :count)
|
||||
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user creation fails' do
|
||||
before do
|
||||
allow(User).to receive(:create).and_return(
|
||||
User.new(email: email).tap do |u|
|
||||
u.errors.add(:email, 'is invalid')
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
it 'redirects to registration with error message' do
|
||||
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[provider]
|
||||
get "/users/auth/#{provider}/callback"
|
||||
|
||||
expect(response).to redirect_to(new_user_registration_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /users/auth/github/callback' do
|
||||
before do
|
||||
mock_github_auth(email: email)
|
||||
end
|
||||
|
||||
include_examples 'successful OAuth authentication', :github, 'GitHub'
|
||||
end
|
||||
|
||||
describe 'GET /users/auth/google_oauth2/callback' do
|
||||
before do
|
||||
mock_google_auth(email: email)
|
||||
end
|
||||
|
||||
include_examples 'successful OAuth authentication', :google_oauth2, 'Google'
|
||||
end
|
||||
|
||||
describe 'GET /users/auth/openid_connect/callback' do
|
||||
before do
|
||||
mock_openid_connect_auth(email: email)
|
||||
end
|
||||
|
||||
include_examples 'successful OAuth authentication', :openid_connect, 'OpenID Connect'
|
||||
end
|
||||
|
||||
describe 'OAuth flow integration' do
|
||||
context 'with GitHub' do
|
||||
before { mock_github_auth(email: 'github@example.com') }
|
||||
|
||||
it 'completes the full OAuth flow' do
|
||||
# Simulate OAuth callback
|
||||
expect do
|
||||
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:github]
|
||||
get '/users/auth/github/callback'
|
||||
end.to change(User, :count).by(1)
|
||||
|
||||
# Verify user is created
|
||||
user = User.find_by(email: 'github@example.com')
|
||||
expect(user).to be_present
|
||||
expect(user.email).to eq('github@example.com')
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with Google' do
|
||||
before { mock_google_auth(email: 'google@example.com') }
|
||||
|
||||
it 'completes the full OAuth flow' do
|
||||
# Simulate OAuth callback
|
||||
expect do
|
||||
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:google_oauth2]
|
||||
get '/users/auth/google_oauth2/callback'
|
||||
end.to change(User, :count).by(1)
|
||||
|
||||
# Verify user is created
|
||||
user = User.find_by(email: 'google@example.com')
|
||||
expect(user).to be_present
|
||||
expect(user.email).to eq('google@example.com')
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with OpenID Connect (Authelia/Authentik)' do
|
||||
before { mock_openid_connect_auth(email: 'oidc@example.com') }
|
||||
|
||||
it 'completes the full OAuth flow' do
|
||||
# Simulate OAuth callback
|
||||
expect do
|
||||
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:openid_connect]
|
||||
get '/users/auth/openid_connect/callback'
|
||||
end.to change(User, :count).by(1)
|
||||
|
||||
# Verify user is created
|
||||
user = User.find_by(email: 'oidc@example.com')
|
||||
expect(user).to be_present
|
||||
expect(user.email).to eq('oidc@example.com')
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'CSRF protection' do
|
||||
it 'does not raise CSRF error for GitHub callback' do
|
||||
mock_github_auth(email: email)
|
||||
|
||||
expect do
|
||||
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:github]
|
||||
get '/users/auth/github/callback'
|
||||
end.not_to raise_error
|
||||
end
|
||||
|
||||
it 'does not raise CSRF error for Google callback' do
|
||||
mock_google_auth(email: email)
|
||||
|
||||
expect do
|
||||
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:google_oauth2]
|
||||
get '/users/auth/google_oauth2/callback'
|
||||
end.not_to raise_error
|
||||
end
|
||||
|
||||
it 'does not raise CSRF error for OpenID Connect callback' do
|
||||
mock_openid_connect_auth(email: email)
|
||||
|
||||
expect do
|
||||
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:openid_connect]
|
||||
get '/users/auth/openid_connect/callback'
|
||||
end.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
105
spec/support/omniauth.rb
Normal file
105
spec/support/omniauth.rb
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
OmniAuth.config.test_mode = true
|
||||
|
||||
module OmniauthHelpers
|
||||
def mock_github_auth(email: 'test@github.com')
|
||||
OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new({
|
||||
provider: 'github',
|
||||
uid: '123545',
|
||||
info: {
|
||||
email: email,
|
||||
name: 'Test User',
|
||||
image: 'https://avatars.githubusercontent.com/u/123545'
|
||||
},
|
||||
credentials: {
|
||||
token: 'mock_token',
|
||||
expires_at: Time.now + 1.week
|
||||
},
|
||||
extra: {
|
||||
raw_info: {
|
||||
login: 'testuser',
|
||||
avatar_url: 'https://avatars.githubusercontent.com/u/123545',
|
||||
name: 'Test User',
|
||||
email: email
|
||||
}
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
def mock_google_auth(email: 'test@gmail.com')
|
||||
OmniAuth.config.mock_auth[:google_oauth2] = OmniAuth::AuthHash.new({
|
||||
provider: 'google_oauth2',
|
||||
uid: '123545',
|
||||
info: {
|
||||
email: email,
|
||||
name: 'Test User',
|
||||
image: 'https://lh3.googleusercontent.com/a/test'
|
||||
},
|
||||
credentials: {
|
||||
token: 'mock_token',
|
||||
refresh_token: 'mock_refresh_token',
|
||||
expires_at: Time.now + 1.hour
|
||||
},
|
||||
extra: {
|
||||
raw_info: {
|
||||
email: email,
|
||||
email_verified: true,
|
||||
name: 'Test User',
|
||||
given_name: 'Test',
|
||||
family_name: 'User',
|
||||
picture: 'https://lh3.googleusercontent.com/a/test'
|
||||
}
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
def mock_openid_connect_auth(email: 'test@oidc.com', provider_name: 'Authelia')
|
||||
OmniAuth.config.mock_auth[:openid_connect] = OmniAuth::AuthHash.new({
|
||||
provider: 'openid_connect',
|
||||
uid: '123545',
|
||||
info: {
|
||||
email: email,
|
||||
name: 'Test User',
|
||||
image: 'https://example.com/avatar.jpg'
|
||||
},
|
||||
credentials: {
|
||||
token: 'mock_token',
|
||||
refresh_token: 'mock_refresh_token',
|
||||
expires_at: Time.now + 1.hour,
|
||||
id_token: 'mock_id_token'
|
||||
},
|
||||
extra: {
|
||||
raw_info: {
|
||||
sub: '123545',
|
||||
email: email,
|
||||
email_verified: true,
|
||||
name: 'Test User',
|
||||
preferred_username: 'testuser',
|
||||
given_name: 'Test',
|
||||
family_name: 'User',
|
||||
picture: 'https://example.com/avatar.jpg'
|
||||
}
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
def mock_oauth_failure(provider)
|
||||
OmniAuth.config.mock_auth[provider] = :invalid_credentials
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.include OmniauthHelpers, type: :request
|
||||
config.include OmniauthHelpers, type: :system
|
||||
|
||||
config.before do
|
||||
OmniAuth.config.test_mode = true
|
||||
end
|
||||
|
||||
config.after do
|
||||
OmniAuth.config.mock_auth[:github] = nil
|
||||
OmniAuth.config.mock_auth[:google_oauth2] = nil
|
||||
OmniAuth.config.mock_auth[:openid_connect] = nil
|
||||
end
|
||||
end
|
||||
Loading…
Reference in a new issue