Compare commits

...
3 changed files with 37 additions and 3 deletions
@@ -21,4 +21,11 @@ class Microsoft::CallbacksController < OauthCallbackController
def imap_login_identity def imap_login_identity
users_data['preferred_username'] || users_data['upn'] || super users_data['preferred_username'] || users_data['upn'] || super
end end
# Prefer the actual `email` claim. Microsoft work/school accounts without a mailbox
# omit it, returning only `preferred_username`/`upn`; fall back to those so inbox
# creation does not fail on a null email.
def email_address
super || users_data['preferred_username'] || users_data['upn']
end
end end
+10 -3
View File
@@ -41,7 +41,7 @@ class OauthCallbackController < ApplicationController
end end
def find_channel_by_email def find_channel_by_email
Channel::Email.find_by(email: users_data['email'], account: account) Channel::Email.find_by(email: email_address, account: account)
end end
def update_channel(channel_email) def update_channel(channel_email)
@@ -64,6 +64,13 @@ class OauthCallbackController < ApplicationController
users_data['email'] users_data['email']
end end
# Email address stored on the Channel::Email record and used to match an existing
# channel. Defaults to the id_token's `email` claim; providers override when that
# claim can be absent and a different one carries the address.
def email_address
users_data['email']
end
def provider_name def provider_name
raise NotImplementedError raise NotImplementedError
end end
@@ -74,7 +81,7 @@ class OauthCallbackController < ApplicationController
def create_channel_with_inbox def create_channel_with_inbox
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
channel_email = Channel::Email.create!(email: users_data['email'], account: account) channel_email = Channel::Email.create!(email: email_address, account: account)
account.inboxes.create!( account.inboxes.create!(
account: account, account: account,
@@ -119,7 +126,7 @@ class OauthCallbackController < ApplicationController
# Fallback name, for when name field is missing from users_data # Fallback name, for when name field is missing from users_data
def fallback_name def fallback_name
users_data['email'].split('@').first.parameterize.titleize email_address.to_s.split('@').first.parameterize.titleize
end end
def oauth_code def oauth_code
@@ -53,6 +53,26 @@ RSpec.describe 'Microsoft::CallbacksController', type: :request do
expect(channel.email).to eq mailbox expect(channel.email).to eq mailbox
end end
it 'falls back to preferred_username for the channel email when the id_token has no email claim' do
upn = 'testaccount@primary-domain.example'
response_body = {
id_token: JWT.encode({ preferred_username: upn, name: 'test' }, nil, 'none'),
access_token: SecureRandom.hex(10), token_type: 'Bearer', refresh_token: SecureRandom.hex(10)
}
stub_request(:post, 'https://login.microsoftonline.com/common/oauth2/v2.0/token')
.with(body: { 'code' => code, 'grant_type' => 'authorization_code',
'redirect_uri' => "#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/microsoft/callback" })
.to_return(status: 200, body: response_body.to_json, headers: { 'Content-Type' => 'application/json' })
get microsoft_callback_url, params: { code: code, state: state }
expect(response).to redirect_to app_email_inbox_agents_url(account_id: account.id, inbox_id: account.inboxes.last.id)
expect(account.inboxes.count).to be 1
channel = account.inboxes.last.channel
expect(channel.email).to eq upn
expect(channel.imap_login).to eq upn
end
it 'creates updates inbox channel config if inbox exists and authentication is successful' do it 'creates updates inbox channel config if inbox exists and authentication is successful' do
inbox = create(:channel_email, account: account, email: email)&.inbox inbox = create(:channel_email, account: account, email: email)&.inbox
expect(inbox.channel.provider_config).to eq({}) expect(inbox.channel.provider_config).to eq({})