forked from OpenNeo/impress
Actually create user from NeoPass authentication! <3 <3
Whew, exciting! Still done nothing against the live NeoPass server, but we've got this fully working with the development server, it seems! Wowie!! This is all still hidden behind secret flags, so it's fine to deploy live. (And it's not actually a problem if someone gets past to the endpoints behind it, because we haven't actually set up real credentials for our NeoPass client yet, so authentication will fail!) Okay time to lie down lol.
This commit is contained in:
parent
31a11a04fa
commit
3eeb5d1065
5 changed files with 116 additions and 10 deletions
|
@ -1,9 +1,48 @@
|
||||||
class Devise::OmniauthCallbacksController < ApplicationController
|
class Devise::OmniauthCallbacksController < ApplicationController
|
||||||
|
rescue_from AuthUser::MissingAuthInfoError, with: :missing_auth_info
|
||||||
|
rescue_from ActiveRecord::RecordInvalid, with: :validation_failed
|
||||||
|
|
||||||
def neopass
|
def neopass
|
||||||
render plain: request.env["omniauth.auth"].uid
|
@auth_user = AuthUser.from_omniauth(request.env["omniauth.auth"])
|
||||||
|
|
||||||
|
if @auth_user.previously_new_record?
|
||||||
|
flash[:notice] =
|
||||||
|
"Welcome to Dress to Impress, #{@auth_user.name}! We've set up a DTI " +
|
||||||
|
"account for you. Click Settings in the top right to learn more, or " +
|
||||||
|
"just go ahead and get started!"
|
||||||
|
else
|
||||||
|
flash[:notice] = "Welcome back, #{@auth_user.name}! 💖"
|
||||||
|
end
|
||||||
|
|
||||||
|
sign_in_and_redirect @auth_user, event: :authentication
|
||||||
end
|
end
|
||||||
|
|
||||||
def failure
|
def failure
|
||||||
render plain: "Failure"
|
flash[:warning] =
|
||||||
|
"Hrm, something went wrong in our connection to NeoPass, sorry! " +
|
||||||
|
"Maybe wait a moment and try again?"
|
||||||
|
redirect_to new_auth_user_session_path
|
||||||
|
end
|
||||||
|
|
||||||
|
def missing_auth_info(error)
|
||||||
|
Sentry.capture_exception error
|
||||||
|
Rails.logger.error error.full_message
|
||||||
|
|
||||||
|
flash[:warning] =
|
||||||
|
"Hrm, our connection with NeoPass didn't return the information we " +
|
||||||
|
"usually expect, sorry! If this keeps happening, please email me at " +
|
||||||
|
"matchu@openneo.net so I can help investigate!"
|
||||||
|
redirect_to new_auth_user_session_path
|
||||||
|
end
|
||||||
|
|
||||||
|
def validation_failed(error)
|
||||||
|
Sentry.capture_exception error
|
||||||
|
Rails.logger.error error.full_message
|
||||||
|
|
||||||
|
flash[:warning] =
|
||||||
|
"Hrm, the connection with NeoPass worked, but we had trouble saving " +
|
||||||
|
"your account, sorry! If this keeps happening, please email me at " +
|
||||||
|
"matchu@openneo.net so I can help investigate!"
|
||||||
|
redirect_to new_auth_user_session_path
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,4 +27,59 @@ class AuthUser < AuthRecord
|
||||||
user.name = name
|
user.name = name
|
||||||
user.save!
|
user.save!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def uses_omniauth?
|
||||||
|
provider? && uid?
|
||||||
|
end
|
||||||
|
|
||||||
|
def email_required?
|
||||||
|
!uses_omniauth?
|
||||||
|
end
|
||||||
|
|
||||||
|
def password_required?
|
||||||
|
!uses_omniauth?
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.from_omniauth(auth)
|
||||||
|
raise MissingAuthInfoError, "Username missing" if auth.uid.blank?
|
||||||
|
raise MissingAuthInfoError, "Email missing" if auth.info.email.blank?
|
||||||
|
|
||||||
|
transaction do
|
||||||
|
find_or_create_by!(provider: auth.provider, uid: auth.uid) do |user|
|
||||||
|
# Use the Neopets username if possible, or a unique username if not.
|
||||||
|
dti_username = build_unique_username_like(auth.uid)
|
||||||
|
user.name = dti_username
|
||||||
|
|
||||||
|
# Copy the email address from their Neopets account to their DTI
|
||||||
|
# account, unless they already have a DTI account with this email, in
|
||||||
|
# which case, ignore it. (It's primarily for their own convenience with
|
||||||
|
# password recovery!)
|
||||||
|
email_exists = AuthUser.where(email: auth.info.email).exists?
|
||||||
|
user.email = auth.info.email unless email_exists
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.build_unique_username_like(name)
|
||||||
|
name_query = sanitize_sql_like(name) + "%"
|
||||||
|
similar_names = where("name LIKE ?", name_query).pluck(:name)
|
||||||
|
|
||||||
|
# Use the given name itself, if we can.
|
||||||
|
return name unless similar_names.include?(name)
|
||||||
|
|
||||||
|
# If not, try appending "-neopass".
|
||||||
|
return "#{name}-neopass" unless similar_names.include?("#{name}-neopass")
|
||||||
|
|
||||||
|
# After that, try appending "-neopass-1", "-neopass-2", etc, until a
|
||||||
|
# unique name arises. (We don't expect this to happen basically ever, but
|
||||||
|
# it's nice to have a guarantee!)
|
||||||
|
max = similar_names.size + 1
|
||||||
|
candidates = (1..max).map { |n| "#{name}-neopass-#{n}"}
|
||||||
|
numerical_name = candidates.find { |name| !similar_names.include?(name) }
|
||||||
|
return numerical_name unless numerical_name.nil?
|
||||||
|
|
||||||
|
raise "Failed to build unique username (shouldn't be possible?)"
|
||||||
|
end
|
||||||
|
|
||||||
|
class MissingAuthInfoError < ArgumentError;end
|
||||||
end
|
end
|
|
@ -21,9 +21,10 @@ const urlLib = require("node:url");
|
||||||
const { OAuth2Server } = require("oauth2-mock-server");
|
const { OAuth2Server } = require("oauth2-mock-server");
|
||||||
const express = require("express");
|
const express = require("express");
|
||||||
|
|
||||||
// This is the Neopets username we'll report back to DTI when you authenticate
|
// This is the Neopets username and email we'll report back to DTI when you
|
||||||
// through here.
|
// authenticate through here.
|
||||||
const USERNAME = "theneopetsteam";
|
const USERNAME = "test";
|
||||||
|
const EMAIL = "theneopetsteam@neopets.com";
|
||||||
|
|
||||||
const certPath = pathLib.join(__dirname, "..", "tmp", "localhost.pem");
|
const certPath = pathLib.join(__dirname, "..", "tmp", "localhost.pem");
|
||||||
const keyPath = pathLib.join(__dirname, "..", "tmp", "localhost-key.pem");
|
const keyPath = pathLib.join(__dirname, "..", "tmp", "localhost-key.pem");
|
||||||
|
@ -88,10 +89,14 @@ async function startServer(port) {
|
||||||
n: "svVfGU4NGcfBCmQiIOW5uzg5SAN2CWSIQSstnhqZoCdjy5OoKpKVR8O9TbDvxixrvkFyAav90Q0Xse8iFTcjfCKuqINYiuYMXhCvfBlc_DVVOQca9pMpN03LaDofd5Ll4_BFTtt1nSPahwWU7xDM-Bkkh_TcS2qS4N2xbpEGi0q0ZkrJN4WyiDBC2k9WbK-YHr4Rj4JKypFVSeBIrjxVPmlPzgfqlLGGIB0l92SnJDXDMlkWcCCTyLgqSBM04nkxGDSykq_ei76qCdRd7b10wMBaoS9DeBThAyHpur2LoPdH3gxbcwoWExi-jPlNP1LdKVZD8b95OY3CRyMAAMGdKQ",
|
n: "svVfGU4NGcfBCmQiIOW5uzg5SAN2CWSIQSstnhqZoCdjy5OoKpKVR8O9TbDvxixrvkFyAav90Q0Xse8iFTcjfCKuqINYiuYMXhCvfBlc_DVVOQca9pMpN03LaDofd5Ll4_BFTtt1nSPahwWU7xDM-Bkkh_TcS2qS4N2xbpEGi0q0ZkrJN4WyiDBC2k9WbK-YHr4Rj4JKypFVSeBIrjxVPmlPzgfqlLGGIB0l92SnJDXDMlkWcCCTyLgqSBM04nkxGDSykq_ei76qCdRd7b10wMBaoS9DeBThAyHpur2LoPdH3gxbcwoWExi-jPlNP1LdKVZD8b95OY3CRyMAAMGdKQ",
|
||||||
});
|
});
|
||||||
|
|
||||||
server.service.on("beforeTokenSigning", (token, req) => {
|
server.service.on("beforeTokenSigning", (token) => {
|
||||||
token.payload.sub = USERNAME;
|
token.payload.sub = USERNAME;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.service.on("beforeUserinfo", (userInfoResponse) => {
|
||||||
|
userInfoResponse.body.email = EMAIL;
|
||||||
|
});
|
||||||
|
|
||||||
await server.start(port, "localhost");
|
await server.start(port, "localhost");
|
||||||
console.log(`Started NeoPass development server at: ${server.issuer.url}`);
|
console.log(`Started NeoPass development server at: ${server.issuer.url}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
class AllowNullEmailAndPasswordForUsers < ActiveRecord::Migration[7.1]
|
||||||
|
def change
|
||||||
|
change_column_null :users, :email, true
|
||||||
|
change_column_null :users, :encrypted_password, true
|
||||||
|
change_column_null :users, :password_salt, true
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,12 +10,12 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.1].define(version: 2024_03_13_200849) do
|
ActiveRecord::Schema[7.1].define(version: 2024_03_15_020053) do
|
||||||
create_table "users", id: { type: :integer, unsigned: true }, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t|
|
create_table "users", id: { type: :integer, unsigned: true }, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t|
|
||||||
t.string "name", limit: 20, null: false
|
t.string "name", limit: 20, null: false
|
||||||
t.string "encrypted_password", limit: 64, null: false
|
t.string "encrypted_password", limit: 64
|
||||||
t.string "email", limit: 50, null: false
|
t.string "email", limit: 50
|
||||||
t.string "password_salt", limit: 32, null: false
|
t.string "password_salt", limit: 32
|
||||||
t.string "reset_password_token"
|
t.string "reset_password_token"
|
||||||
t.integer "sign_in_count", default: 0
|
t.integer "sign_in_count", default: 0
|
||||||
t.datetime "current_sign_in_at", precision: nil
|
t.datetime "current_sign_in_at", precision: nil
|
||||||
|
|
Loading…
Reference in a new issue