مراية لـ
https://github.com/postalserver/postal.git
تم المزامنة 2026-01-16 21:23:37 +00:00
feat: openid connect support (#2873)
هذا الالتزام موجود في:
@@ -1,22 +1,32 @@
|
||||
.loginForm {
|
||||
|
||||
}
|
||||
.loginForm {}
|
||||
|
||||
|
||||
.loginForm__input {
|
||||
margin-bottom:15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.loginForm__submit {
|
||||
display:flex;
|
||||
justify-content:space-between;
|
||||
align-items:center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
.loginForm__links {
|
||||
font-size:12px;
|
||||
color:#999;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
text-decoration: underline;
|
||||
line-height:1.7;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.loginForm__divider {
|
||||
margin-top: 25px;
|
||||
margin-bottom: 25px;
|
||||
border-top: 1px solid #e4e8ef;
|
||||
}
|
||||
|
||||
.loginForm__localTitle {
|
||||
text-align: center;
|
||||
margin-bottom: 15px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
@@ -1,74 +1,67 @@
|
||||
.userList {
|
||||
border-radius:4px;
|
||||
color:$darkBlue;
|
||||
overflow:hidden;
|
||||
box-shadow:0 0 10px rgba(0,0,0,0.2);
|
||||
border-radius: 4px;
|
||||
color: $darkBlue;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.userList__item {
|
||||
display:block;
|
||||
background:#fff;
|
||||
padding:15px;
|
||||
display:flex;
|
||||
display: block;
|
||||
background: #fff;
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.userList__item:nth-child(even) {
|
||||
background:none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.userList__item + .userList__item {
|
||||
border-top:1px solid lighten(#ccd4e0, 10%);
|
||||
}
|
||||
|
||||
.userList__avatar {
|
||||
width:50px;
|
||||
height:50px;
|
||||
border-radius:50%;
|
||||
background:#fff;
|
||||
border:2px solid #efefef;
|
||||
padding:3px;
|
||||
flex: 0 0 auto;
|
||||
.userList__item+.userList__item {
|
||||
border-top: 1px solid lighten(#ccd4e0, 10%);
|
||||
}
|
||||
|
||||
.userList__details {
|
||||
flex: 1 1 auto;
|
||||
margin:0 25px;
|
||||
margin: 0 0;
|
||||
}
|
||||
|
||||
|
||||
.userList__actions {
|
||||
flex: 0 0 auto;
|
||||
width:180px;
|
||||
font-size:12px;
|
||||
line-height:1.5;
|
||||
color:#999;
|
||||
text-decoration: underline;
|
||||
width: 120px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: #999;
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.userList__name {
|
||||
font-weight:600;
|
||||
font-size:16px;
|
||||
margin-bottom:3px;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.userList__owner {
|
||||
vertical-align:2px;
|
||||
margin-left:5px;
|
||||
background-color:$orange;
|
||||
vertical-align: 2px;
|
||||
margin-left: 5px;
|
||||
background-color: $orange;
|
||||
}
|
||||
|
||||
.userList__pending {
|
||||
vertical-align:2px;
|
||||
margin-left:5px;
|
||||
background-color:#ccc;
|
||||
vertical-align: 2px;
|
||||
margin-left: 5px;
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
.userList__admin {
|
||||
vertical-align:2px;
|
||||
margin-left:5px;
|
||||
background-color:$blue;
|
||||
.userList__tag {
|
||||
vertical-align: 2px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.userList__revoke {
|
||||
color:$red;
|
||||
color: $red;
|
||||
}
|
||||
|
||||
@@ -1,98 +1,117 @@
|
||||
.button {
|
||||
display:inline-block;
|
||||
font:inherit;
|
||||
border-radius:4px;
|
||||
appearance:none;
|
||||
background:$blue;
|
||||
color:#fff;
|
||||
font-size:14px !important;
|
||||
margin:0;
|
||||
vertical-align:top;
|
||||
padding:6px 15px;
|
||||
border:2px solid transparent;
|
||||
border-bottom:2px solid darken($blue, 20%);
|
||||
display: inline-block;
|
||||
font: inherit;
|
||||
border-radius: 4px;
|
||||
appearance: none;
|
||||
background: $blue;
|
||||
color: #fff;
|
||||
font-size: 14px !important;
|
||||
margin: 0;
|
||||
vertical-align: top;
|
||||
padding: 6px 15px;
|
||||
border: 2px solid transparent;
|
||||
border-bottom: 2px solid darken($blue, 20%);
|
||||
|
||||
&:active {
|
||||
background-color:darken($blue, 15%);
|
||||
background-color: darken($blue, 15%);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color:darken($blue, 15%);
|
||||
background-color:lighten($blue, 5%);
|
||||
border-color: darken($blue, 15%);
|
||||
background-color: lighten($blue, 5%);
|
||||
}
|
||||
|
||||
&.is-spinning {
|
||||
color:transparent;
|
||||
background-repeat:no-repeat;
|
||||
color: transparent;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size:25px;
|
||||
background-image:image-url('button-spinner.gif');
|
||||
background-size: 25px;
|
||||
background-image: image-url('button-spinner.gif');
|
||||
}
|
||||
}
|
||||
|
||||
.button--small {
|
||||
font-size:12px !important;
|
||||
padding:3px 10px;
|
||||
border-width:1px;
|
||||
font-size: 12px !important;
|
||||
padding: 3px 10px;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.button--positive {
|
||||
background-color:$green;
|
||||
border-bottom-color:darken($green, 15%);
|
||||
background-color: $green;
|
||||
border-bottom-color: darken($green, 15%);
|
||||
|
||||
&:active {
|
||||
background-color:darken($green, 15%);
|
||||
background-color: darken($green, 15%);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color:darken($green, 15%);
|
||||
background-color:lighten($green, 5%);
|
||||
border-color: darken($green, 15%);
|
||||
background-color: lighten($green, 5%);
|
||||
}
|
||||
|
||||
&.is-spinning {
|
||||
background-image:image-url('button-spinner-positive.gif');
|
||||
background-image: image-url('button-spinner-positive.gif');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.button--neutral {
|
||||
background-color:#ccc;
|
||||
border-bottom-color:darken(#ccc, 15%);
|
||||
background-color: #ccc;
|
||||
border-bottom-color: darken(#ccc, 15%);
|
||||
|
||||
&:active {
|
||||
background-color:darken(#ccc, 15%);
|
||||
background-color: darken(#ccc, 15%);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color:darken(#ccc, 15%);
|
||||
background-color:lighten(#ccc, 5%);
|
||||
border-color: darken(#ccc, 15%);
|
||||
background-color: lighten(#ccc, 5%);
|
||||
}
|
||||
|
||||
&.is-spinning {
|
||||
background-image:image-url('button-spinner-neutral.gif');
|
||||
background-image: image-url('button-spinner-neutral.gif');
|
||||
}
|
||||
}
|
||||
|
||||
.button--danger {
|
||||
background-color:$red;
|
||||
border-bottom-color:darken($red, 15%);
|
||||
background-color: $red;
|
||||
border-bottom-color: darken($red, 15%);
|
||||
|
||||
&:active {
|
||||
background-color:darken($red, 15%);
|
||||
background-color: darken($red, 15%);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color:darken($red, 15%);
|
||||
background-color:lighten($red, 5%);
|
||||
border-color: darken($red, 15%);
|
||||
background-color: lighten($red, 5%);
|
||||
}
|
||||
|
||||
&.is-spinning {
|
||||
background-image:image-url('button-spinner-danger.gif');
|
||||
background-image: image-url('button-spinner-danger.gif');
|
||||
}
|
||||
}
|
||||
|
||||
.button--dark {
|
||||
background-color:$darkBlue;
|
||||
border-bottom-color:darken($darkBlue, 15%);
|
||||
background-color: $darkBlue;
|
||||
border-bottom-color: darken($darkBlue, 15%);
|
||||
|
||||
&:active {
|
||||
background-color:darken($darkBlue, 15%);
|
||||
background-color: darken($darkBlue, 15%);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color:darken($darkBlue, 15%);
|
||||
background-color:lighten($darkBlue, 5%);
|
||||
border-color: darken($darkBlue, 15%);
|
||||
background-color: lighten($darkBlue, 5%);
|
||||
}
|
||||
|
||||
&.is-spinning {
|
||||
background-image:image-url('button-spinner-dark.gif');
|
||||
background-image: image-url('button-spinner-dark.gif');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.button--full {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -1,136 +1,137 @@
|
||||
.label {
|
||||
display:inline-block;
|
||||
background:#000;
|
||||
color:#fff;
|
||||
font-size:9px;
|
||||
display: inline-block;
|
||||
background: #000;
|
||||
color: #fff;
|
||||
font-size: 9px;
|
||||
text-transform: uppercase;
|
||||
border-radius:40px;
|
||||
padding:2px 6px;
|
||||
line-height:0.9;
|
||||
border-radius: 40px;
|
||||
padding: 2px 6px;
|
||||
line-height: 0.9;
|
||||
}
|
||||
|
||||
.label--green {
|
||||
background-color:$green;
|
||||
background-color: $green;
|
||||
}
|
||||
|
||||
.label--red {
|
||||
background-color:$red;
|
||||
background-color: $red;
|
||||
}
|
||||
|
||||
.label--orange {
|
||||
background-color:$orange;
|
||||
background-color: $orange;
|
||||
}
|
||||
|
||||
.label--blue {
|
||||
background-color:$blue;
|
||||
background-color: $blue;
|
||||
}
|
||||
|
||||
.label--grey {
|
||||
background-color:#999;
|
||||
background-color: #999;
|
||||
}
|
||||
|
||||
.label--turquoise {
|
||||
background-color:$blue;
|
||||
background-color: $blue;
|
||||
}
|
||||
|
||||
.label--purple {
|
||||
background-color:$purple;
|
||||
background-color: $purple;
|
||||
}
|
||||
|
||||
.label--large {
|
||||
font-size:11px;
|
||||
padding:4px 10px;
|
||||
font-size: 11px;
|
||||
padding: 4px 10px;
|
||||
}
|
||||
|
||||
.label--serverStatus-live {
|
||||
background-color:$green;
|
||||
background-color: $green;
|
||||
}
|
||||
|
||||
.label--serverStatus-development {
|
||||
background-color:#636363;
|
||||
background-color: #636363;
|
||||
}
|
||||
|
||||
.label--serverStatus-suspended {
|
||||
background-color:$red;
|
||||
background-color: $red;
|
||||
}
|
||||
|
||||
.label--messageStatus-pending {
|
||||
background-color:$subBlue;
|
||||
background-color: $subBlue;
|
||||
}
|
||||
|
||||
.label--messageStatus-held {
|
||||
background-color:#aaa;
|
||||
background-color: #aaa;
|
||||
}
|
||||
|
||||
.label--messageStatus-processed {
|
||||
background-color:$green;
|
||||
background-color: $green;
|
||||
}
|
||||
|
||||
.label--messageStatus-sent {
|
||||
background-color:$green;
|
||||
background-color: $green;
|
||||
}
|
||||
|
||||
.label--messageStatus-hard_fail {
|
||||
background-color:$red;
|
||||
background-color: $red;
|
||||
}
|
||||
|
||||
.label--messageStatus-soft_fail {
|
||||
background-color:$orange;
|
||||
background-color: $orange;
|
||||
}
|
||||
|
||||
.label--messageStatus-bounced {
|
||||
background-color:$red;
|
||||
background-color: $red;
|
||||
}
|
||||
|
||||
.label--messageStatus-hold_cancelled {
|
||||
background-color:#ccc;
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
|
||||
.label--credentialType-api {
|
||||
background-color:$blue;
|
||||
background-color: $blue;
|
||||
}
|
||||
|
||||
.label--credentialType-smtp {
|
||||
background-color:$turquoise;
|
||||
background-color: $turquoise;
|
||||
}
|
||||
|
||||
.label--credentialType-smtp_ip {
|
||||
background-color:$orange;
|
||||
background-color: $orange;
|
||||
}
|
||||
|
||||
.label--spamStatus-not_checked {
|
||||
background:#aaa;
|
||||
background: #aaa;
|
||||
}
|
||||
|
||||
.label--spamStatus-spam {
|
||||
background:$orange;
|
||||
background: $orange;
|
||||
}
|
||||
|
||||
.label--spamStatus-not_spam {
|
||||
background:$turquoise;
|
||||
background: $turquoise;
|
||||
}
|
||||
|
||||
.label--http-status-2 {
|
||||
background-color:$green;
|
||||
background-color: $green;
|
||||
}
|
||||
|
||||
.label--http-status-3 {
|
||||
background-color:$orange;
|
||||
background-color: $orange;
|
||||
}
|
||||
|
||||
.label--http-status-4,
|
||||
.label--http-status-5 {
|
||||
background-color:$red;
|
||||
background-color: $red;
|
||||
}
|
||||
|
||||
.domainList__ssl {
|
||||
color:$green;
|
||||
color: $green;
|
||||
|
||||
&:hover {
|
||||
text-decoration:underline;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.domainList__ssl--disabled {
|
||||
color:#999;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ class SessionsController < ApplicationController
|
||||
|
||||
layout "sub"
|
||||
|
||||
skip_before_action :login_required, only: [:new, :create, :begin_password_reset, :finish_password_reset, :ip, :raise_error]
|
||||
before_action :require_local_authentication, only: [:create, :begin_password_reset, :finish_password_reset]
|
||||
skip_before_action :login_required, only: [:new, :create, :begin_password_reset, :finish_password_reset, :ip, :raise_error, :create_from_oidc, :oauth_failure]
|
||||
|
||||
def create
|
||||
login(User.authenticate(params[:email_address], params[:password]))
|
||||
@@ -29,12 +30,16 @@ class SessionsController < ApplicationController
|
||||
def begin_password_reset
|
||||
return unless request.post?
|
||||
|
||||
if user = User.where(email_address: params[:email_address]).first
|
||||
user.begin_password_reset(params[:return_to])
|
||||
redirect_to login_path(return_to: params[:return_to]), notice: "Please check your e-mail and click the link in the e-mail we've sent you."
|
||||
else
|
||||
redirect_to login_reset_path(return_to: params[:return_to]), alert: "No user exists with that e-mail address. Please check and try again."
|
||||
user_scope = Postal::Config.oidc.enabled? ? User.with_password : User
|
||||
user = user_scope.find_by(email_address: params[:email_address])
|
||||
|
||||
if user.nil?
|
||||
redirect_to login_reset_path(return_to: params[:return_to]), alert: "No local user exists with that e-mail address. Please check and try again."
|
||||
return
|
||||
end
|
||||
|
||||
user.begin_password_reset(params[:return_to])
|
||||
redirect_to login_path(return_to: params[:return_to]), notice: "Please check your e-mail and click the link in the e-mail we've sent you."
|
||||
end
|
||||
|
||||
def finish_password_reset
|
||||
@@ -49,6 +54,7 @@ class SessionsController < ApplicationController
|
||||
flash.now[:alert] = "You must enter a new password"
|
||||
return
|
||||
end
|
||||
|
||||
@user.password = params[:password]
|
||||
@user.password_confirmation = params[:password_confirmation]
|
||||
return unless @user.save
|
||||
@@ -61,4 +67,33 @@ class SessionsController < ApplicationController
|
||||
render plain: "ip: #{request.ip} remote ip: #{request.remote_ip}"
|
||||
end
|
||||
|
||||
def create_from_oidc
|
||||
unless Postal::Config.oidc.enabled?
|
||||
raise Postal::Error, "OIDC cannot be used unless enabled in the configuration"
|
||||
end
|
||||
|
||||
auth = request.env["omniauth.auth"]
|
||||
user = User.find_from_oidc(auth.extra.raw_info, logger: Postal.logger)
|
||||
if user.nil?
|
||||
redirect_to login_path, alert: "No user was found matching your identity. Please contact your administrator."
|
||||
return
|
||||
end
|
||||
|
||||
login(user)
|
||||
flash[:remember_login] = true
|
||||
redirect_to_with_return_to root_path
|
||||
end
|
||||
|
||||
def oauth_failure
|
||||
redirect_to login_path, alert: "An issue occurred while logging you in with OpenID. Please try again later or contact your administrator."
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_local_authentication
|
||||
return if Postal::Config.oidc.local_authentication_enabled?
|
||||
|
||||
redirect_to login_path, alert: "Local authentication is not enabled"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -30,23 +30,28 @@ class UserController < ApplicationController
|
||||
|
||||
def update
|
||||
@user = User.find(current_user.id)
|
||||
@user.attributes = params.require(:user).permit(:first_name, :last_name, :time_zone, :email_address, :password, :password_confirmation)
|
||||
safe_params = [:first_name, :last_name, :time_zone, :email_address]
|
||||
|
||||
if @user.authenticate_with_previous_password_first(params[:password])
|
||||
@password_correct = true
|
||||
else
|
||||
respond_to do |wants|
|
||||
wants.html do
|
||||
flash.now[:alert] = "The current password you have entered is incorrect. Please check and try again."
|
||||
render "edit"
|
||||
end
|
||||
wants.json do
|
||||
render json: { alert: "The current password you've entered is incorrect. Please check and try again" }
|
||||
if @user.password? && Postal::Config.oidc.local_authentication_enabled?
|
||||
safe_params += [:password, :password_confirmation]
|
||||
if @user.authenticate_with_previous_password_first(params[:password])
|
||||
@password_correct = true
|
||||
else
|
||||
respond_to do |wants|
|
||||
wants.html do
|
||||
flash.now[:alert] = "The current password you have entered is incorrect. Please check and try again."
|
||||
render "edit"
|
||||
end
|
||||
wants.json do
|
||||
render json: { alert: "The current password you've entered is incorrect. Please check and try again" }
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
@user.attributes = params.require(:user).permit(safe_params)
|
||||
|
||||
if @user.save
|
||||
redirect_to_with_json settings_path, notice: "Your settings have been updated successfully."
|
||||
else
|
||||
|
||||
@@ -5,15 +5,20 @@ module HasAuthentication
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_secure_password
|
||||
has_secure_password validations: false
|
||||
|
||||
validates :password, length: { minimum: 8, allow_blank: true }
|
||||
validates :password, confirmation: { allow_blank: true }
|
||||
validate :validate_password_presence
|
||||
|
||||
before_save :clear_password_reset_token_on_password_change
|
||||
|
||||
scope :with_password, -> { where.not(password_digest: nil) }
|
||||
end
|
||||
|
||||
class_methods do
|
||||
def authenticate(email_address, password)
|
||||
user = where(email_address: email_address).first
|
||||
user = find_by(email_address: email_address)
|
||||
raise Postal::Errors::AuthenticationError, "InvalidEmailAddress" if user.nil?
|
||||
raise Postal::Errors::AuthenticationError, "InvalidPassword" unless user.authenticate(password)
|
||||
|
||||
@@ -30,6 +35,10 @@ module HasAuthentication
|
||||
end
|
||||
|
||||
def begin_password_reset(return_to = nil)
|
||||
if Postal::Config.oidc.enabled? && (oidc_uid.present? || password_digest.blank?)
|
||||
raise Postal::Error, "User has OIDC enabled, password resets are not supported"
|
||||
end
|
||||
|
||||
self.password_reset_token = SecureRandom.alphanumeric(24)
|
||||
self.password_reset_token_valid_until = 1.day.from_now
|
||||
save!
|
||||
@@ -45,6 +54,12 @@ module HasAuthentication
|
||||
self.password_reset_token_valid_until = nil
|
||||
end
|
||||
|
||||
def validate_password_presence
|
||||
return if password_digest.present? || Postal::Config.oidc.enabled?
|
||||
|
||||
errors.add :password, :blank
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# -*- SkipSchemaAnnotations
|
||||
|
||||
@@ -5,19 +5,21 @@
|
||||
# Table name: users
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# uuid :string(255)
|
||||
# first_name :string(255)
|
||||
# last_name :string(255)
|
||||
# admin :boolean default(FALSE)
|
||||
# email_address :string(255)
|
||||
# password_digest :string(255)
|
||||
# time_zone :string(255)
|
||||
# email_verification_token :string(255)
|
||||
# email_verified_at :datetime
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# first_name :string(255)
|
||||
# last_name :string(255)
|
||||
# oidc_issuer :string(255)
|
||||
# oidc_uid :string(255)
|
||||
# password_digest :string(255)
|
||||
# password_reset_token :string(255)
|
||||
# password_reset_token_valid_until :datetime
|
||||
# admin :boolean default(FALSE)
|
||||
# time_zone :string(255)
|
||||
# uuid :string(255)
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
@@ -28,13 +30,11 @@
|
||||
class User < ApplicationRecord
|
||||
|
||||
include HasUUID
|
||||
|
||||
include HasAuthentication
|
||||
|
||||
validates :first_name, presence: true
|
||||
validates :last_name, presence: true
|
||||
validates :email_address, presence: true, uniqueness: { case_sensitive: false }, format: { with: /@/, allow_blank: true }
|
||||
validates :time_zone, presence: true
|
||||
|
||||
default_value :time_zone, -> { "UTC" }
|
||||
|
||||
@@ -53,24 +53,85 @@ class User < ApplicationRecord
|
||||
"#{first_name} #{last_name}"
|
||||
end
|
||||
|
||||
def password?
|
||||
password_digest.present?
|
||||
end
|
||||
|
||||
def oidc?
|
||||
oidc_uid.present?
|
||||
end
|
||||
|
||||
def to_param
|
||||
uuid
|
||||
end
|
||||
|
||||
def md5_for_gravatar
|
||||
@md5_for_gravatar ||= Digest::MD5.hexdigest(email_address.to_s.downcase)
|
||||
end
|
||||
|
||||
def avatar_url
|
||||
@avatar_url ||= email_address ? "https://secure.gravatar.com/avatar/#{md5_for_gravatar}?rating=PG&size=120&d=mm" : nil
|
||||
end
|
||||
|
||||
def email_tag
|
||||
"#{name} <#{email_address}>"
|
||||
end
|
||||
|
||||
def self.[](email)
|
||||
where(email_address: email).first
|
||||
class << self
|
||||
|
||||
# Lookup a user by email address
|
||||
#
|
||||
# @param email [String] the email address
|
||||
#
|
||||
# @return [User, nil] the user
|
||||
def [](email)
|
||||
find_by(email_address: email)
|
||||
end
|
||||
|
||||
# Find a user based on an OIDC authentication hash
|
||||
#
|
||||
# @param auth [Hash] the authentication hash
|
||||
# @param logger [Logger] a logger to log debug information to
|
||||
#
|
||||
# @return [User, nil] the user
|
||||
def find_from_oidc(auth, logger: nil)
|
||||
config = Postal::Config.oidc
|
||||
|
||||
uid = auth[config.uid_field]
|
||||
oidc_name = auth[config.name_field]
|
||||
oidc_email_address = auth[config.email_address_field]
|
||||
|
||||
logger&.debug "got auth details from issuer: #{auth.inspect}"
|
||||
|
||||
# look for an existing user with the same UID and OIDC issuer. If we find one,
|
||||
# this is the user we'll want to use.
|
||||
user = where(oidc_uid: uid, oidc_issuer: config.issuer).first
|
||||
|
||||
if user
|
||||
logger&.debug "found user with UID #{uid} for issuer #{config.issuer} (user ID: #{user.id})"
|
||||
else
|
||||
logger&.debug "no user with UID #{uid} for issuer #{config.issuer}"
|
||||
end
|
||||
|
||||
# if we don't have an existing user, we will look for users which have no OIDC
|
||||
# credentials but with a matching e-mail address.
|
||||
if user.nil? && oidc_email_address.present?
|
||||
user = where(oidc_uid: nil, email_address: oidc_email_address).first
|
||||
if user
|
||||
logger&.debug "found user with e-mail address #{oidc_email_address} (user ID: #{user.id})"
|
||||
else
|
||||
logger&.debug "no user with e-mail address #{oidc_email_address}"
|
||||
end
|
||||
end
|
||||
|
||||
# now, if we still don't have a user, we're not going to create one so we'll just
|
||||
# return nil (we might auto create users in the future but not right now)
|
||||
return if user.nil?
|
||||
|
||||
# otherwise, let's update our user as appropriate
|
||||
user.oidc_uid = uid
|
||||
user.oidc_issuer = config.issuer
|
||||
user.email_address = oidc_email_address if oidc_email_address.present?
|
||||
user.first_name, user.last_name = oidc_name.split(/\s+/, 2) if oidc_name.present?
|
||||
user.password = nil
|
||||
user.save!
|
||||
|
||||
# return the user
|
||||
user
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -6,13 +6,19 @@
|
||||
.subPageBox__content
|
||||
= form_tag login_path, :class => 'loginForm' do
|
||||
= hidden_field_tag 'return_to', params[:return_to]
|
||||
- if params[:return_to] && params[:return_to] =~ /\/join\//
|
||||
%p.loginForm__invite.warningBox.u-margin Login to your existing account to accept your invitation.
|
||||
|
||||
%p.loginForm__input= text_field_tag 'email_address', '', :type => 'email', :autocomplete => 'off', :spellcheck => 'false', :class => 'input input--text input--onWhite', :placeholder => "Your e-mail address", :autofocus => true, :tabindex => 1
|
||||
%p.loginForm__input= password_field_tag 'password', '', :class => 'input input--text input--onWhite', :placeholder => "Your password", :tabindex => 2
|
||||
.loginForm__submit
|
||||
%ul.loginForm__links
|
||||
%li= link_to "Forgotten your password?", login_reset_path(:return_to => params[:return_to])
|
||||
%p= submit_tag "Login", :class => 'button button--positive', :tabindex => 3
|
||||
- if Postal::Config.oidc.enabled?
|
||||
.loginForm__oidcButton
|
||||
= link_to "Login with #{Postal::Config.oidc.name}", "/auth/oidc", method: :post, class: 'button button--full'
|
||||
|
||||
- if Postal::Config.oidc.enabled? && Postal::Config.oidc.local_authentication_enabled?
|
||||
.loginForm__divider
|
||||
%p.loginForm__localTitle or login with a local user
|
||||
|
||||
- if Postal::Config.oidc.local_authentication_enabled?
|
||||
%p.loginForm__input= text_field_tag 'email_address', '', :type => 'email', :spellcheck => 'false', :class => 'input input--text input--onWhite', :placeholder => "Your e-mail address", :autofocus => !Postal::Config.oidc.enabled?, :tabindex => 1
|
||||
%p.loginForm__input= password_field_tag 'password', '', :class => 'input input--text input--onWhite', :placeholder => "Your password", :tabindex => 2
|
||||
.loginForm__submit
|
||||
%ul.loginForm__links
|
||||
%li= link_to "Forgotten your password?", login_reset_path(:return_to => params[:return_to])
|
||||
%p= submit_tag "Login", :class => 'button button--positive', :tabindex => 3
|
||||
|
||||
@@ -6,15 +6,16 @@
|
||||
= form_for @user, :url => settings_path, :remote => true do |f|
|
||||
= f.error_messages
|
||||
%fieldset.fieldSet
|
||||
.fieldSet__field
|
||||
= label_tag :password, 'Your Password', :class => 'fieldSet__label'
|
||||
.fieldSet__input
|
||||
= password_field_tag :password, params[:password], :autofocus => @password_correct.nil?, :disabled => @password_correct, :class => 'input input--text', :placeholder => "Enter your current password to change your details"
|
||||
- if @password_correct
|
||||
= hidden_field_tag :password, params[:password]
|
||||
%p.fieldSet__text
|
||||
In order to protect your account, you need to enter your current password in the field above
|
||||
to authenticate the change of your details.
|
||||
- if @user.password? && Postal::Config.oidc.local_authentication_enabled?
|
||||
.fieldSet__field
|
||||
= label_tag :password, 'Your Password', :class => 'fieldSet__label'
|
||||
.fieldSet__input
|
||||
= password_field_tag :password, params[:password], :autofocus => @password_correct.nil?, :disabled => @password_correct, :class => 'input input--text', :placeholder => "Enter your current password to change your details"
|
||||
- if @password_correct
|
||||
= hidden_field_tag :password, params[:password]
|
||||
%p.fieldSet__text
|
||||
In order to protect your account, you need to enter your current password in the field above
|
||||
to authenticate the change of your details.
|
||||
|
||||
.fieldSet__title
|
||||
Your details
|
||||
@@ -41,14 +42,15 @@
|
||||
Choose the time zone that you'd like times to be displayed to you when you use our
|
||||
web interface. By default, times are displayed in UTC.
|
||||
|
||||
.fieldSet__title
|
||||
Change your password?
|
||||
.fieldSet__field
|
||||
= f.label :password, "New Password", :class => 'fieldSet__label'
|
||||
.fieldSet__input
|
||||
.inputPair
|
||||
= f.password_field :password, :class => 'input input--text', :placeholder => "•••••••••••", :value => @user.password
|
||||
= f.password_field :password_confirmation, :class => 'input input--text', :placeholder => "and confirm it", :value => @user.password_confirmation
|
||||
- if @user.password? && Postal::Config.oidc.local_authentication_enabled?
|
||||
.fieldSet__title
|
||||
Change your password?
|
||||
.fieldSet__field
|
||||
= f.label :password, "New Password", :class => 'fieldSet__label'
|
||||
.fieldSet__input
|
||||
.inputPair
|
||||
= f.password_field :password, :class => 'input input--text', :placeholder => "•••••••••••", :value => @user.password
|
||||
= f.password_field :password_confirmation, :class => 'input input--text', :placeholder => "and confirm it", :value => @user.password_confirmation
|
||||
|
||||
|
||||
%p.fieldSetSubmit.buttonSet
|
||||
|
||||
@@ -11,11 +11,25 @@
|
||||
.fieldSet__input= f.text_field :last_name, :class => 'input input--text'
|
||||
.fieldSet__field
|
||||
= f.label :email_address, :class => 'fieldSet__label'
|
||||
.fieldSet__input= f.text_field :email_address, :class => 'input input--text', autocomplete: 'one-time-code'
|
||||
- unless @user.persisted?
|
||||
.fieldSet__input
|
||||
= f.text_field :email_address, :class => 'input input--text', autocomplete: 'one-time-code'
|
||||
- if Postal::Config.oidc.enabled?
|
||||
%p.fieldSet__text
|
||||
This e-mail address should match the address provided by your OpenID Connect identity provider.
|
||||
|
||||
|
||||
- if Postal::Config.oidc.local_authentication_enabled? && !@user.persisted?
|
||||
.fieldSet__field
|
||||
= f.label :password, :class => 'fieldSet__label'
|
||||
.fieldSet__input= f.password_field :password, :class => 'input input--text', :placeholder => '•••••••••••', autocomplete: 'one-time-code'
|
||||
.fieldSet__input
|
||||
= f.password_field :password, :class => 'input input--text', :placeholder => '•••••••••••', autocomplete: 'one-time-code'
|
||||
- if Postal::Config.oidc.enabled?
|
||||
%p.fieldSet__text
|
||||
You have enabled OIDC which means a password is not required. If you do not provide
|
||||
a password this user will be matched to an OIDC identity based on the e-mail address
|
||||
provided above. You may, however, enter a password and this user will be permitted to
|
||||
use that password until they have successfully logged in with OIDC.
|
||||
|
||||
.fieldSet__field
|
||||
= f.label :password_confirmation, "Confirm".html_safe, :class => 'fieldSet__label'
|
||||
.fieldSet__input= f.password_field :password_confirmation, :class => 'input input--text', :placeholder => '•••••••••••', autocomplete: 'one-time-code'
|
||||
|
||||
@@ -7,15 +7,20 @@
|
||||
%ul.userList.u-margin
|
||||
- for user in @users
|
||||
%li.userList__item
|
||||
= image_tag user.avatar_url, :class => 'userList__avatar'
|
||||
.userList__details
|
||||
%p.userList__name
|
||||
= user.name
|
||||
- if user.admin?
|
||||
%span.userList__admin.label Admin
|
||||
%span.userList__tag.label.label--blue Admin
|
||||
- if Postal::Config.oidc.enabled?
|
||||
- if user.oidc?
|
||||
%span.userList__tag.label.label--green OIDC
|
||||
- elsif !Postal::Config.oidc.local_authentication_enabled?
|
||||
%span.userList__tag.label.label--orange Pending
|
||||
|
||||
%p.userList__email= user.email_address
|
||||
%ul.userList__actions
|
||||
%li= link_to "Edit permissions", [:edit, user]
|
||||
%li= link_to "Edit user", [:edit, user]
|
||||
%li= link_to "Delete user", user, :method => :delete, :data => {:confirm => "Are you sure you wish to revoke #{user.name}'s access?", :disable_with => "Deleting..."}, :remote => true, :class => 'userList__revoke'
|
||||
|
||||
%p.u-center= link_to "Add a new user", :new_user, :class => 'button button--positive'
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم