From e4f5c0d3e0c0bdff84942dd39ab0de567ee626f9 Mon Sep 17 00:00:00 2001 From: koladev Date: Mon, 21 Jul 2025 06:05:06 +0100 Subject: [PATCH 1/8] add ruby imports scripts --- ruby-on-rails/Gemfile | 6 + ruby-on-rails/README.md | 74 +++++++++ ruby-on-rails/import.rb | 332 ++++++++++++++++++++++++++++++++++++++++ ruby-on-rails/import.sh | 25 +++ 4 files changed, 437 insertions(+) create mode 100644 ruby-on-rails/Gemfile create mode 100644 ruby-on-rails/README.md create mode 100755 ruby-on-rails/import.rb create mode 100755 ruby-on-rails/import.sh diff --git a/ruby-on-rails/Gemfile b/ruby-on-rails/Gemfile new file mode 100644 index 0000000..5e70b61 --- /dev/null +++ b/ruby-on-rails/Gemfile @@ -0,0 +1,6 @@ +source 'https://rubygems.org' + +gem 'fusionauth_client', '~> 1.51' +gem 'json', '~> 2.6' +gem 'optparse', '~> 0.4' +gem 'base64', '~> 0.2' \ No newline at end of file diff --git a/ruby-on-rails/README.md b/ruby-on-rails/README.md new file mode 100644 index 0000000..8941a76 --- /dev/null +++ b/ruby-on-rails/README.md @@ -0,0 +1,74 @@ +# FusionAuth Import Script for Ruby on Rails + +A script to import user data from Rails authentication systems into FusionAuth. Features duplicate handling, verbose logging, and social account linking for OmniAuth integrations. + +## Prerequisites + +1. **FusionAuth Instance**: Running FusionAuth server (default: ) +2. **API Key**: FusionAuth API key with user import permissions +3. **Ruby**: Ruby 2.7+ with bundler +4. **Export File**: JSON export file from your Rails auth system. Refer to the [FusionAuth documentation](https://fusionauth.io/docs/apis/users#import-users) for details. + +## Installation + +1. Install dependencies: + + ```bash + bundle install + ``` + +2. Make the script executable: + + ```bash + chmod +x import.rb + ``` + +3. (Optional) Make the wrapper script executable: + + ```bash + chmod +x import.sh + ``` + +## Usage + +```bash +./import.rb -k YOUR_API_KEY -u users_export_file.json +``` + +This imports users from the file into FusionAuth's default tenant and application. + +### Advanced Usage + +```bash +./import.rb \ + --fusionauth-api-key YOUR_API_KEY \ + --users-file users_export_file.json \ + --fusionauth-url http://localhost:9011 \ + --fusionauth-tenant-id YOUR_TENANT_ID \ + --source-system devise \ + --register-users app-id-1,app-id-2,app-id-3 \ + --link-social-accounts \ + --verbose +``` + +## Command Line Options + +| Option | Short | Required | Default | Description | +|--------|-------|----------|---------|-------------| +| `--users-file` | `-u` | No | `users.json` | Path to the exported JSON user file | +| `--fusionauth-api-key` | `-k` | **Yes** | - | FusionAuth API key | +| `--fusionauth-url` | `-f` | No | `http://localhost:9011` | FusionAuth instance URL | +| `--fusionauth-tenant-id` | `-t` | No* | - | Tenant ID (required if multiple tenants exist) | +| `--source-system` | `-s` | No | Auto-detected | Source system: devise, rails_auth, omniauth | +| `--register-users` | `-r` | No | - | Comma-separated list of application IDs | +| `--link-social-accounts` | `-l` | No | `false` | Link social accounts for OmniAuth users | +| `--verbose` | `-v` | No | `false` | Enable detailed logging | +| `--help` | `-h` | No | - | Show help message | + +## Supported Authentication Systems + +**Devise**: Imports users, encrypted passwords, and user metadata. Preserves email confirmation status and account locking. + +**Rails Auth**: Imports users, confirmation status, and sign-in tracking data. + +**OmniAuth**: Imports social accounts with identity provider linking. Specify social provider and provider user ID in the `user[x].data` field. diff --git a/ruby-on-rails/import.rb b/ruby-on-rails/import.rb new file mode 100755 index 0000000..8ee2627 --- /dev/null +++ b/ruby-on-rails/import.rb @@ -0,0 +1,332 @@ +#!/usr/bin/env ruby -w + +require 'date' +require 'json' +require 'fusionauth/fusionauth_client' +require 'optparse' +require 'securerandom' +require 'set' + +# Option handling +options = {} + +# Default options +options[:usersfile] = "users.json" +options[:fusionauthurl] = "http://localhost:9011" + +OptionParser.new do |opts| + opts.banner = "Usage: import.rb [options]" + + opts.on("-u", "--users-file USERS_FILE", "The exported JSON user data file from Rails auth systems. Defaults to users.json.") do |file| + options[:usersfile] = file + end + + opts.on("-f", "--fusionauth-url FUSIONAUTH_URL", "The location of the FusionAuth instance. Defaults to http://localhost:9011.") do |fusionauthurl| + options[:fusionauthurl] = fusionauthurl + end + + opts.on("-k", "--fusionauth-api-key API_KEY", "The FusionAuth API key.") do |fusionauthapikey| + options[:fusionauthapikey] = fusionauthapikey + end + + opts.on("-t", "--fusionauth-tenant-id TENANT_ID", "The FusionAuth tenant id. Required if more than one tenant exists.") do |tenantid| + options[:tenantid] = tenantid + end + + opts.on("-s", "--source-system SOURCE", "The source authentication system (devise, rails_auth, omniauth). Auto-detected if not specified.") do |source| + options[:sourcesystem] = source + end + + opts.on("-r", "--register-users APPLICATION_IDS", "A comma separated list of existing application IDs. All users will be registered for these applications.") do |appids| + options[:appids] = appids + end + + opts.on("-l", "--link-social-accounts", "Link social accounts for OmniAuth users after import.") do |linksocial| + options[:linksocial] = true + end + + opts.on("-v", "--verbose", "Enable verbose logging.") do |verbose| + options[:verbose] = true + end + + opts.on("-h", "--help", "Prints this help.") do + puts opts + exit + end +end.parse! + +# Validate required options +if options[:fusionauthapikey].nil? + puts "Error: FusionAuth API key is required. Use -k or --fusionauth-api-key" + exit 1 +end + +users_file = options[:usersfile] +$fusionauth_url = options[:fusionauthurl] +$fusionauth_api_key = options[:fusionauthapikey] +$fusionauth_tenant_id = options[:tenantid] +$verbose = options[:verbose] + +puts "FusionAuth Importer : Rails Authentication Systems" +puts " > User file: #{users_file}" +puts " > FusionAuth URL: #{$fusionauth_url}" +puts " > Tenant ID: #{$fusionauth_tenant_id || 'default'}" +puts "" + +# Identity Provider mappings for social accounts +# # ids pulled from https://github.com/FusionAuth/fusionauth-java-client/blob/master/src/main/java/io/fusionauth/domain/provider/IdentityProviderType.java +IDP_MAPPINGS = { + "google-oauth2" => "82339786-3dff-42a6-aac6-1f1ceecb6c46", # Google +} + +def detect_source_system(user_data) + return nil if user_data.empty? + + first_user = user_data.first + + # Check data field for explicit source system + if first_user['data'] && first_user['data']['source_system'] + return first_user['data']['source_system'] + end + + # Auto-detect based on structure + if first_user['data'] && first_user['data']['oauth_provider'] + return 'omniauth' + elsif first_user['password'] && first_user['encryptionScheme'] == 'bcrypt' + if first_user['fullName'] + return 'rails_auth' + else + return 'devise' + end + end + + return 'unknown' +end + +def log_verbose(message) + puts " > #{message}" if $verbose +end + +def add_additional_registrations(users, app_ids) + return users if app_ids.nil? || app_ids.empty? + + additional_app_ids = app_ids.split(',').map(&:strip) + log_verbose("Adding registrations for applications: #{additional_app_ids.join(', ')}") + + users.each do |user| + # Initialize registrations array if it doesn't exist + user['registrations'] ||= [] + + # Add registrations for additional applications + additional_app_ids.each do |app_id| + # Check if user is already registered for this application + unless user['registrations'].any? { |reg| reg['applicationId'] == app_id } + additional_registration = { + 'id' => SecureRandom.uuid, + 'applicationId' => app_id, + 'verified' => user['verified'] || true, + 'roles' => ['user'] + } + user['registrations'] << additional_registration + log_verbose("Added registration for #{user['email']} to application #{app_id}") + end + end + end + + users +end + +def import_users(users) + puts " > Importing #{users.length} users to FusionAuth..." + + import_request = { + 'users' => users, + 'validateDbConstraints' => false + } + + client = FusionAuth::FusionAuthClient.new($fusionauth_api_key, $fusionauth_url) + client.set_tenant_id($fusionauth_tenant_id) if $fusionauth_tenant_id + + response = client.import_users(import_request) + + if response.was_successful + puts " > Import successful!" + return true + else + puts " > Import failed. Status code: #{response.status}" + puts " > Error response: #{response.error_response}" + return false + end +end + +def find_user_by_email(client, email) + results = client.search_users_by_query({search: {queryString: email}}) + + if results && results.success_response + users = results.success_response.users + if users.length == 1 + return users[0].id + elsif users.length > 1 + puts " > Warning: Multiple users found for #{email}. Skipping link." + return nil + end + end + + puts " > Warning: User #{email} not found in FusionAuth. Skipping link." + return nil +end + +def link_social_accounts(social_users) + return if social_users.empty? + + puts " > Linking #{social_users.length} social accounts..." + + client = FusionAuth::FusionAuthClient.new($fusionauth_api_key, $fusionauth_url) + client.set_tenant_id($fusionauth_tenant_id) if $fusionauth_tenant_id + + linked_count = 0 + + social_users.each do |user| + provider = user['data']['oauth_provider'] + oauth_uid = user['data']['oauth_uid'] + email = user['email'] + + # Skip if provider doesn't need linking + next if provider == 'developer' + + identity_provider_id = IDP_MAPPINGS[provider] + unless identity_provider_id + puts " > Warning: No identity provider mapping for #{provider}. Skipping #{email}." + next + end + + # Find the user in FusionAuth + fusionauth_user_id = find_user_by_email(client, email) + next unless fusionauth_user_id + + # Create the link + link_request = { + 'identityProviderId' => identity_provider_id, + 'identityProviderUserId' => oauth_uid, + 'userId' => fusionauth_user_id + } + + log_verbose("Linking #{email} (#{provider}) to FusionAuth user #{fusionauth_user_id}") + + response = client.create_user_link(link_request) + + if response.was_successful + log_verbose("Successfully linked #{email}") + linked_count += 1 + else + puts " > Failed to link #{email}: #{response.error_response}" + end + end + + puts " > Successfully linked #{linked_count} social accounts" +end + +# Main execution +begin + # Read and parse the users file + unless File.exist?(users_file) + puts "Error: Users file '#{users_file}' not found." + exit 1 + end + + file_content = File.read(users_file) + data = JSON.parse(file_content) + + # Extract users array + users_data = data['users'] || data + + if users_data.empty? + puts "Error: No users found in the file." + exit 1 + end + + # Detect source system + source_system = options[:sourcesystem] || detect_source_system(users_data) + puts " > Detected source system: #{source_system}" + puts "" + + # Validate users and collect social accounts + valid_users = [] + social_users = [] + duplicate_emails = [] + emails_seen = Set.new + + users_data.each do |user| + email = user['email'] + + # Check for duplicates + if emails_seen.include?(email) + duplicate_emails << email + next + end + emails_seen.add(email) + + # Collect social users for later linking + if source_system == 'omniauth' && user['data'] && user['data']['oauth_provider'] + social_users << user + end + + valid_users << user + end + + # Report duplicates + if duplicate_emails.any? + puts " > Warning: Found #{duplicate_emails.length} duplicate emails:" + duplicate_emails.each { |email| puts " - #{email}" } + puts "" + end + + puts " > Processing #{valid_users.length} valid users" + + # Add additional application registrations if specified + if options[:appids] + puts " > Adding additional application registrations..." + valid_users = add_additional_registrations(valid_users, options[:appids]) + end + + # Import users in chunks of 10,000 + chunk_size = 10_000 + total_imported = 0 + + valid_users.each_slice(chunk_size) do |chunk| + if import_users(chunk) + total_imported += chunk.length + else + puts "Error: Import failed for chunk. Stopping." + exit 1 + end + end + + puts "" + puts " > Successfully imported #{total_imported} users" + + # Link social accounts if requested and applicable + if options[:linksocial] && source_system == 'omniauth' && social_users.any? + puts "" + link_social_accounts(social_users) + end + + puts "" + puts "Import completed successfully!" + puts " > Total users imported: #{total_imported}" + puts " > Duplicates skipped: #{duplicate_emails.length}" + + if source_system == 'omniauth' && !options[:linksocial] && social_users.any? + puts "" + puts "Note: #{social_users.length} social accounts detected." + puts "Use the --link-social-accounts flag to link them to identity providers." + end + +rescue JSON::ParserError => e + puts "Error: Invalid JSON file. #{e.message}" + exit 1 +rescue => e + puts "Error: #{e.message}" + puts e.backtrace if $verbose + exit 1 +end \ No newline at end of file diff --git a/ruby-on-rails/import.sh b/ruby-on-rails/import.sh new file mode 100755 index 0000000..c66a731 --- /dev/null +++ b/ruby-on-rails/import.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# FusionAuth Import Script Wrapper +# This script makes it easier to run the Ruby import script + +# Get the directory where this script is located +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Check if bundle is available +if ! command -v bundle &> /dev/null; then + echo "Error: Bundler is not installed. Please install it with: gem install bundler" + exit 1 +fi + +# Change to the script directory +cd "$SCRIPT_DIR" + +# Check if gems are installed +if [ ! -d ".bundle" ] && [ ! -f "Gemfile.lock" ]; then + echo "Installing required gems..." + bundle install +fi + +# Run the import script with all passed arguments +bundle exec ./import.rb "$@" \ No newline at end of file From 484eff17e909b07d2c8c9429c618a18ae06c73ce Mon Sep 17 00:00:00 2001 From: koladev Date: Mon, 21 Jul 2025 15:16:19 +0100 Subject: [PATCH 2/8] rename import script dir --- {ruby-on-rails => rails}/Gemfile | 0 {ruby-on-rails => rails}/README.md | 0 {ruby-on-rails => rails}/import.rb | 0 {ruby-on-rails => rails}/import.sh | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {ruby-on-rails => rails}/Gemfile (100%) rename {ruby-on-rails => rails}/README.md (100%) rename {ruby-on-rails => rails}/import.rb (100%) rename {ruby-on-rails => rails}/import.sh (100%) diff --git a/ruby-on-rails/Gemfile b/rails/Gemfile similarity index 100% rename from ruby-on-rails/Gemfile rename to rails/Gemfile diff --git a/ruby-on-rails/README.md b/rails/README.md similarity index 100% rename from ruby-on-rails/README.md rename to rails/README.md diff --git a/ruby-on-rails/import.rb b/rails/import.rb similarity index 100% rename from ruby-on-rails/import.rb rename to rails/import.rb diff --git a/ruby-on-rails/import.sh b/rails/import.sh similarity index 100% rename from ruby-on-rails/import.sh rename to rails/import.sh From 6d727b139b84ed99203e1cb41875c06787da2bf7 Mon Sep 17 00:00:00 2001 From: koladev Date: Wed, 23 Jul 2025 07:54:32 +0100 Subject: [PATCH 3/8] add export scripts and example data --- .../export_users_for_fusionauth.rb | 75 +++++ .../built-in-auth/users_export.json | 30 ++ .../devise/export_users_for_fusionauth.rb | 76 ++++++ rails/export-scripts/devise/users_export.json | 33 +++ .../omniauth/export_users_for_fusionauth.rb | 53 ++++ .../export-scripts/omniauth/users_export.json | 256 ++++++++++++++++++ 6 files changed, 523 insertions(+) create mode 100644 rails/export-scripts/built-in-auth/export_users_for_fusionauth.rb create mode 100644 rails/export-scripts/built-in-auth/users_export.json create mode 100755 rails/export-scripts/devise/export_users_for_fusionauth.rb create mode 100644 rails/export-scripts/devise/users_export.json create mode 100644 rails/export-scripts/omniauth/export_users_for_fusionauth.rb create mode 100644 rails/export-scripts/omniauth/users_export.json diff --git a/rails/export-scripts/built-in-auth/export_users_for_fusionauth.rb b/rails/export-scripts/built-in-auth/export_users_for_fusionauth.rb new file mode 100644 index 0000000..85b7c77 --- /dev/null +++ b/rails/export-scripts/built-in-auth/export_users_for_fusionauth.rb @@ -0,0 +1,75 @@ +#!/usr/bin/env ruby + +# Simple User Export Script for Rails Authentication +# Exports users as plain JSON + +require_relative '../config/environment' +require 'json' +require 'securerandom' + +puts "Starting user export for Rails Authentication users..." +puts "Found #{User.count} users to export" + +users_data = User.all.map do |user| + puts "Exporting user: #{user.email}" + + # Parse bcrypt hash according to FusionAuth requirements: + # Example: $2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy + # Should be split to: + # factor: 10 + # salt: N9qo8uLOickgx2ZMRZoMye (first 22 chars after factor) + # password: IjZAgcfl7p92ldGxad68LJZdL17lhWy (remaining chars) + + bcrypt_factor = 10 # default + bcrypt_salt = "" + bcrypt_password = "" + + if user.password_digest&.match(/^\$2[aby]\$(\d+)\$(.+)$/) + bcrypt_factor = $1.to_i + salt_and_hash = $2 + + # According to the user, salt is first 22 characters + bcrypt_salt = salt_and_hash[0, 22] + # Password is the remaining characters + bcrypt_password = salt_and_hash[22..-1] + end + + user_data = { + email: user.email, + username: user.email, + fullName: user.name, + password: bcrypt_password, + encryptionScheme: "bcrypt", + factor: bcrypt_factor, + salt: bcrypt_salt, + passwordChangeRequired: false, # Add this field like in Akamai example + verified: user.confirmed?, + active: user.confirmed?, + registrations: [ + { + id: SecureRandom.uuid, + applicationId: "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", + verified: user.confirmed?, + roles: ["user"] + } + ], + # Additional user data + data: { + migrated_from: "rails_authentication", + original_id: user.id + } + } + + user_data +end + +# Save to JSON file +filename = "users_export.json" + +File.open(filename, 'w') do |file| + file.write(JSON.pretty_generate(users_data)) +end + +puts "\nExport complete!" +puts "Saved #{users_data.length} users to #{filename}" +puts "Total users exported: #{users_data.count}" \ No newline at end of file diff --git a/rails/export-scripts/built-in-auth/users_export.json b/rails/export-scripts/built-in-auth/users_export.json new file mode 100644 index 0000000..82745d8 --- /dev/null +++ b/rails/export-scripts/built-in-auth/users_export.json @@ -0,0 +1,30 @@ +{ + "users": [ + { + "email": "sarah.johnson@techcorp.com", + "username": "sarah.johnson@techcorp.com", + "fullName": "Sarah Johnson", + "password": "ANzpvmDwOpcY4GU0fpNDsCrB6l9Ad62", + "encryptionScheme": "bcrypt", + "factor": 12, + "salt": "jTZOY/BQkbKJNMaCuy39Cu", + "passwordChangeRequired": false, + "verified": true, + "active": true, + "registrations": [ + { + "id": "72efaea3-267a-49cc-a6f0-aa8f0b096b29", + "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", + "verified": true, + "roles": [ + "user" + ] + } + ], + "data": { + "migrated_from": "rails_authentication", + "original_id": 1 + } + } + ] +} \ No newline at end of file diff --git a/rails/export-scripts/devise/export_users_for_fusionauth.rb b/rails/export-scripts/devise/export_users_for_fusionauth.rb new file mode 100755 index 0000000..fdff395 --- /dev/null +++ b/rails/export-scripts/devise/export_users_for_fusionauth.rb @@ -0,0 +1,76 @@ +#!/usr/bin/env ruby + +# Simple User Export Script for Devise Users +# Exports users as plain JSON + +require_relative 'config/environment' +require 'json' +require 'securerandom' + +puts "Starting user export for Devise users..." +puts "Found #{User.count} users to export" + +users_data = User.all.map do |user| + puts "Exporting user: #{user.email}" + + # Parse bcrypt hash according to FusionAuth requirements: + # Example: $2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy + # Should be split to: + # factor: 10 + # salt: N9qo8uLOickgx2ZMRZoMye (first 22 chars after factor) + # password: IjZAgcfl7p92ldGxad68LJZdL17lhWy (remaining chars) + + bcrypt_factor = 10 # default + bcrypt_salt = "" + bcrypt_password = "" + + if user.encrypted_password&.match(/^\$2[aby]\$(\d+)\$(.+)$/) + bcrypt_factor = $1.to_i + salt_and_hash = $2 + + # According to FusionAuth requirements, salt is first 22 characters + bcrypt_salt = salt_and_hash[0, 22] + # Password is the remaining characters + bcrypt_password = salt_and_hash[22..-1] + end + + user_data = { + email: user.email, + username: user.email, + password: bcrypt_password, + encryptionScheme: "bcrypt", + factor: bcrypt_factor, + salt: bcrypt_salt, + passwordChangeRequired: false, # Add this field like in Akamai example + verified: user.respond_to?(:confirmed?) ? user.confirmed? : true, + active: true, + registrations: [ + { + id: SecureRandom.uuid, + applicationId: "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", + verified: user.respond_to?(:confirmed?) ? user.confirmed? : true, + roles: ["user"] + } + ], + data: { + source_system: "devise", + original_user_id: user.id, + locked_at: user.respond_to?(:locked_at) ? user.locked_at : nil, + confirmation_token: user.respond_to?(:confirmation_token) ? user.confirmation_token : nil, + last_sign_in_ip: user.respond_to?(:last_sign_in_ip) ? user.last_sign_in_ip : nil, + current_sign_in_ip: user.respond_to?(:current_sign_in_ip) ? user.current_sign_in_ip : nil + } + } + + user_data +end + +# Write to file with proper FusionAuth format +export_data = { users: users_data } +filename = "users_export.json" +File.write(filename, JSON.pretty_generate(export_data)) + +puts "" +puts "Export completed successfully!" +puts "File saved as: #{filename}" +puts "Total users exported: #{users_data.count}" \ No newline at end of file diff --git a/rails/export-scripts/devise/users_export.json b/rails/export-scripts/devise/users_export.json new file mode 100644 index 0000000..2e3175a --- /dev/null +++ b/rails/export-scripts/devise/users_export.json @@ -0,0 +1,33 @@ +{ + "users": [ + { + "email": "jennifer.adams@techstart.com", + "username": "jennifer.adams@techstart.com", + "password": "wSMHfHu84ns5n4WeGY0Jn.ZwZsLj3zC", + "encryptionScheme": "bcrypt", + "factor": 12, + "salt": "Oa88b1GmziTesQzGkMSYyu", + "passwordChangeRequired": false, + "verified": true, + "active": true, + "registrations": [ + { + "id": "cc09b582-d35b-44b1-8331-f8c329006079", + "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", + "verified": true, + "roles": [ + "user" + ] + } + ], + "data": { + "source_system": "devise", + "original_user_id": 1, + "locked_at": null, + "confirmation_token": null, + "last_sign_in_ip": null, + "current_sign_in_ip": null + } + } + ] +} \ No newline at end of file diff --git a/rails/export-scripts/omniauth/export_users_for_fusionauth.rb b/rails/export-scripts/omniauth/export_users_for_fusionauth.rb new file mode 100644 index 0000000..3a2db21 --- /dev/null +++ b/rails/export-scripts/omniauth/export_users_for_fusionauth.rb @@ -0,0 +1,53 @@ +#!/usr/bin/env ruby + +# Simple User Export Script for OmniAuth Users +# Exports users as plain JSON + +require_relative 'config/environment' +require 'json' +require 'securerandom' + +puts "Starting user export for OmniAuth users..." +puts "Found #{User.count} users to export" + +users_data = User.all.map do |user| + puts "Exporting user: #{user.email} (#{user.provider})" + + { + email: user.email, + username: user.email, + fullName: user.name, + verified: true, + active: user.active, + imageUrl: user.image_url, + registrations: [ + { + id: SecureRandom.uuid, + applicationId: "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", + verified: true, + roles: ["user"] + } + ], + data: { + source_system: "omniauth", + original_user_id: user.id, + oauth_provider: user.provider, + oauth_uid: user.uid, + locked_at: nil, + confirmation_token: nil, + last_sign_in_ip: nil, + current_sign_in_ip: nil + } + } +end + +# Write to file with proper FusionAuth format +export_data = { users: users_data } +filename = "users_export.json" +File.write(filename, JSON.pretty_generate(export_data)) + +puts "" +puts "Export completed successfully!" +puts "File saved as: #{filename}" +puts "Total users exported: #{users_data.count}" +puts "Providers: #{users_data.group_by { |u| u[:data][:oauth_provider] }.transform_values(&:count)}" \ No newline at end of file diff --git a/rails/export-scripts/omniauth/users_export.json b/rails/export-scripts/omniauth/users_export.json new file mode 100644 index 0000000..e202d92 --- /dev/null +++ b/rails/export-scripts/omniauth/users_export.json @@ -0,0 +1,256 @@ +{ + "users": [ + { + "email": "alexandra.kim@techventures.com", + "username": "alexandra.kim@techventures.com", + "fullName": "Alexandra Kim", + "verified": true, + "active": true, + "imageUrl": "https://via.placeholder.com/80/667eea/ffffff?text=AK", + "registrations": [ + { + "id": "77af93d8-dd28-4a75-83c9-a0577963f846", + "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", + "verified": true, + "roles": [ + "user" + ] + } + ], + "data": { + "source_system": "omniauth", + "original_user_id": 1, + "oauth_provider": "google_oauth2", + "oauth_uid": "108234567890123456789", + "locked_at": null, + "confirmation_token": null, + "last_sign_in_ip": null, + "current_sign_in_ip": null + } + }, + { + "email": "marcus.johnson@creativestudio.net", + "username": "marcus.johnson@creativestudio.net", + "fullName": "Marcus Johnson", + "verified": true, + "active": true, + "imageUrl": "https://via.placeholder.com/80/764ba2/ffffff?text=MJ", + "registrations": [ + { + "id": "f5e01422-5269-4351-ba95-73eae37d1eda", + "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", + "verified": true, + "roles": [ + "user" + ] + } + ], + "data": { + "source_system": "omniauth", + "original_user_id": 2, + "oauth_provider": "google_oauth2", + "oauth_uid": "109876543210987654321", + "locked_at": null, + "confirmation_token": null, + "last_sign_in_ip": null, + "current_sign_in_ip": null + } + }, + { + "email": "sofia.rodriguez@datainsights.io", + "username": "sofia.rodriguez@datainsights.io", + "fullName": "Sofia Rodriguez", + "verified": true, + "active": true, + "imageUrl": "https://via.placeholder.com/80/f093fb/ffffff?text=SR", + "registrations": [ + { + "id": "9e81ae71-1c9b-4765-8e48-c9ceabc8d0d4", + "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", + "verified": true, + "roles": [ + "user" + ] + } + ], + "data": { + "source_system": "omniauth", + "original_user_id": 3, + "oauth_provider": "google_oauth2", + "oauth_uid": "107123456789012345678", + "locked_at": null, + "confirmation_token": null, + "last_sign_in_ip": null, + "current_sign_in_ip": null + } + }, + { + "email": "chen.wei@startupaccel.cn", + "username": "chen.wei@startupaccel.cn", + "fullName": "Chen Wei", + "verified": true, + "active": true, + "imageUrl": "https://via.placeholder.com/80/4facfe/ffffff?text=CW", + "registrations": [ + { + "id": "495ef475-5265-4aed-b649-2b40afc78d89", + "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", + "verified": true, + "roles": [ + "user" + ] + } + ], + "data": { + "source_system": "omniauth", + "original_user_id": 4, + "oauth_provider": "google_oauth2", + "oauth_uid": "106789012345678901234", + "locked_at": null, + "confirmation_token": null, + "last_sign_in_ip": null, + "current_sign_in_ip": null + } + }, + { + "email": "emma.thompson@freelancedesign.co.uk", + "username": "emma.thompson@freelancedesign.co.uk", + "fullName": "Emma Thompson", + "verified": true, + "active": true, + "imageUrl": "https://via.placeholder.com/80/43e97b/ffffff?text=ET", + "registrations": [ + { + "id": "37483b05-4db6-4db8-8dc6-74b2af480a9c", + "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", + "verified": true, + "roles": [ + "user" + ] + } + ], + "data": { + "source_system": "omniauth", + "original_user_id": 5, + "oauth_provider": "google_oauth2", + "oauth_uid": "105456789012345678901", + "locked_at": null, + "confirmation_token": null, + "last_sign_in_ip": null, + "current_sign_in_ip": null + } + }, + { + "email": "raj.patel@consultingfirm.in", + "username": "raj.patel@consultingfirm.in", + "fullName": "Raj Patel", + "verified": true, + "active": true, + "imageUrl": "https://via.placeholder.com/80/f6ad55/ffffff?text=RP", + "registrations": [ + { + "id": "14d2519a-34fa-4ffd-8f58-090b0224cdbb", + "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", + "verified": true, + "roles": [ + "user" + ] + } + ], + "data": { + "source_system": "omniauth", + "original_user_id": 6, + "oauth_provider": "google_oauth2", + "oauth_uid": "104321098765432109876", + "locked_at": null, + "confirmation_token": null, + "last_sign_in_ip": null, + "current_sign_in_ip": null + } + }, + { + "email": "inactive.oauth@oldaccount.com", + "username": "inactive.oauth@oldaccount.com", + "fullName": "Inactive OAuth User", + "verified": true, + "active": false, + "imageUrl": "https://via.placeholder.com/80/6c757d/ffffff?text=IO", + "registrations": [ + { + "id": "242c38e0-ebbc-4510-b5b5-046acf42aaf3", + "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", + "verified": true, + "roles": [ + "user" + ] + } + ], + "data": { + "source_system": "omniauth", + "original_user_id": 7, + "oauth_provider": "google_oauth2", + "oauth_uid": "103987654321098765432", + "locked_at": null, + "confirmation_token": null, + "last_sign_in_ip": null, + "current_sign_in_ip": null + } + }, + { + "email": "dev.tester@localhost.dev", + "username": "dev.tester@localhost.dev", + "fullName": "Development Tester", + "verified": true, + "active": true, + "imageUrl": "https://via.placeholder.com/80/28a745/ffffff?text=DEV", + "registrations": [ + { + "id": "d9f060d5-23de-4baf-846e-3ada2fb93fd9", + "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", + "verified": true, + "roles": [ + "user" + ] + } + ], + "data": { + "source_system": "omniauth", + "original_user_id": 8, + "oauth_provider": "developer", + "oauth_uid": "dev.tester@localhost.dev", + "locked_at": null, + "confirmation_token": null, + "last_sign_in_ip": null, + "current_sign_in_ip": null + } + }, + { + "email": "qa.engineer@testingteam.local", + "username": "qa.engineer@testingteam.local", + "fullName": "QA Engineer", + "verified": true, + "active": true, + "imageUrl": "https://via.placeholder.com/80/17a2b8/ffffff?text=QA", + "registrations": [ + { + "id": "a877b73e-e123-49c5-a73e-a4687f0f6055", + "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", + "verified": true, + "roles": [ + "user" + ] + } + ], + "data": { + "source_system": "omniauth", + "original_user_id": 9, + "oauth_provider": "developer", + "oauth_uid": "qa.engineer@testingteam.local", + "locked_at": null, + "confirmation_token": null, + "last_sign_in_ip": null, + "current_sign_in_ip": null + } + } + ] +} \ No newline at end of file From 96927c329e79a7b126ffe99d1da45b5e41908c01 Mon Sep 17 00:00:00 2001 From: koladev Date: Fri, 25 Jul 2025 03:11:01 +0100 Subject: [PATCH 4/8] fix: fix scripts --- .../omniauth/export_users_for_fusionauth.rb | 1 + .../export-scripts/omniauth/users_export.json | 27 ++++++---- rails/import.rb | 49 ++++++++++--------- 3 files changed, 44 insertions(+), 33 deletions(-) diff --git a/rails/export-scripts/omniauth/export_users_for_fusionauth.rb b/rails/export-scripts/omniauth/export_users_for_fusionauth.rb index 3a2db21..1e3950a 100644 --- a/rails/export-scripts/omniauth/export_users_for_fusionauth.rb +++ b/rails/export-scripts/omniauth/export_users_for_fusionauth.rb @@ -17,6 +17,7 @@ email: user.email, username: user.email, fullName: user.name, + password: SecureRandom.alphanumeric(12) + "!", verified: true, active: user.active, imageUrl: user.image_url, diff --git a/rails/export-scripts/omniauth/users_export.json b/rails/export-scripts/omniauth/users_export.json index e202d92..3a33d60 100644 --- a/rails/export-scripts/omniauth/users_export.json +++ b/rails/export-scripts/omniauth/users_export.json @@ -4,12 +4,13 @@ "email": "alexandra.kim@techventures.com", "username": "alexandra.kim@techventures.com", "fullName": "Alexandra Kim", + "password": "oceghPW47j1f!", "verified": true, "active": true, "imageUrl": "https://via.placeholder.com/80/667eea/ffffff?text=AK", "registrations": [ { - "id": "77af93d8-dd28-4a75-83c9-a0577963f846", + "id": "d068cd77-4c94-470f-bc83-399b1b63023a", "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", "verified": true, "roles": [ @@ -32,12 +33,13 @@ "email": "marcus.johnson@creativestudio.net", "username": "marcus.johnson@creativestudio.net", "fullName": "Marcus Johnson", + "password": "8yuwWM680qlB!", "verified": true, "active": true, "imageUrl": "https://via.placeholder.com/80/764ba2/ffffff?text=MJ", "registrations": [ { - "id": "f5e01422-5269-4351-ba95-73eae37d1eda", + "id": "1f2980fc-7ac4-49a4-8686-33fec97dd757", "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", "verified": true, "roles": [ @@ -60,12 +62,13 @@ "email": "sofia.rodriguez@datainsights.io", "username": "sofia.rodriguez@datainsights.io", "fullName": "Sofia Rodriguez", + "password": "lgPNgTIGYzt6!", "verified": true, "active": true, "imageUrl": "https://via.placeholder.com/80/f093fb/ffffff?text=SR", "registrations": [ { - "id": "9e81ae71-1c9b-4765-8e48-c9ceabc8d0d4", + "id": "8c6d8346-fe5d-4542-9211-f0e471866d33", "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", "verified": true, "roles": [ @@ -88,12 +91,13 @@ "email": "chen.wei@startupaccel.cn", "username": "chen.wei@startupaccel.cn", "fullName": "Chen Wei", + "password": "7ARgRrd1zryN!", "verified": true, "active": true, "imageUrl": "https://via.placeholder.com/80/4facfe/ffffff?text=CW", "registrations": [ { - "id": "495ef475-5265-4aed-b649-2b40afc78d89", + "id": "fd92c192-428c-45a0-90ac-ef8cbf516978", "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", "verified": true, "roles": [ @@ -116,12 +120,13 @@ "email": "emma.thompson@freelancedesign.co.uk", "username": "emma.thompson@freelancedesign.co.uk", "fullName": "Emma Thompson", + "password": "yikMeaYPs9Q6!", "verified": true, "active": true, "imageUrl": "https://via.placeholder.com/80/43e97b/ffffff?text=ET", "registrations": [ { - "id": "37483b05-4db6-4db8-8dc6-74b2af480a9c", + "id": "47cd443f-e0cc-465c-93a9-4cb0f5008206", "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", "verified": true, "roles": [ @@ -144,12 +149,13 @@ "email": "raj.patel@consultingfirm.in", "username": "raj.patel@consultingfirm.in", "fullName": "Raj Patel", + "password": "Unq8sPOZJtFV!", "verified": true, "active": true, "imageUrl": "https://via.placeholder.com/80/f6ad55/ffffff?text=RP", "registrations": [ { - "id": "14d2519a-34fa-4ffd-8f58-090b0224cdbb", + "id": "d14eb917-d98b-460a-b6ca-662fb327b77f", "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", "verified": true, "roles": [ @@ -172,12 +178,13 @@ "email": "inactive.oauth@oldaccount.com", "username": "inactive.oauth@oldaccount.com", "fullName": "Inactive OAuth User", + "password": "RsNPbaBVUwR9!", "verified": true, "active": false, "imageUrl": "https://via.placeholder.com/80/6c757d/ffffff?text=IO", "registrations": [ { - "id": "242c38e0-ebbc-4510-b5b5-046acf42aaf3", + "id": "e2696e20-1216-458b-991f-4b9c881eb4bb", "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", "verified": true, "roles": [ @@ -200,12 +207,13 @@ "email": "dev.tester@localhost.dev", "username": "dev.tester@localhost.dev", "fullName": "Development Tester", + "password": "6bIv8xDX67uV!", "verified": true, "active": true, "imageUrl": "https://via.placeholder.com/80/28a745/ffffff?text=DEV", "registrations": [ { - "id": "d9f060d5-23de-4baf-846e-3ada2fb93fd9", + "id": "815b74f9-e3f5-47d3-b7de-68300273de2e", "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", "verified": true, "roles": [ @@ -228,12 +236,13 @@ "email": "qa.engineer@testingteam.local", "username": "qa.engineer@testingteam.local", "fullName": "QA Engineer", + "password": "7zioKbQLhYs6!", "verified": true, "active": true, "imageUrl": "https://via.placeholder.com/80/17a2b8/ffffff?text=QA", "registrations": [ { - "id": "a877b73e-e123-49c5-a73e-a4687f0f6055", + "id": "ea0f348a-1803-475a-987d-7f062f087825", "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", "verified": true, "roles": [ diff --git a/rails/import.rb b/rails/import.rb index 8ee2627..58f47b5 100755 --- a/rails/import.rb +++ b/rails/import.rb @@ -76,7 +76,7 @@ # Identity Provider mappings for social accounts # # ids pulled from https://github.com/FusionAuth/fusionauth-java-client/blob/master/src/main/java/io/fusionauth/domain/provider/IdentityProviderType.java IDP_MAPPINGS = { - "google-oauth2" => "82339786-3dff-42a6-aac6-1f1ceecb6c46", # Google + "google_oauth2" => "82339786-3dff-42a6-aac6-1f1ceecb6c46", # Google } def detect_source_system(user_data) @@ -159,24 +159,9 @@ def import_users(users) end end -def find_user_by_email(client, email) - results = client.search_users_by_query({search: {queryString: email}}) - - if results && results.success_response - users = results.success_response.users - if users.length == 1 - return users[0].id - elsif users.length > 1 - puts " > Warning: Multiple users found for #{email}. Skipping link." - return nil - end - end - - puts " > Warning: User #{email} not found in FusionAuth. Skipping link." - return nil -end -def link_social_accounts(social_users) + +def link_social_accounts(social_users, user_id_mapping) return if social_users.empty? puts " > Linking #{social_users.length} social accounts..." @@ -200,15 +185,19 @@ def link_social_accounts(social_users) next end - # Find the user in FusionAuth - fusionauth_user_id = find_user_by_email(client, email) - next unless fusionauth_user_id + # Get the user ID from our mapping + fusionauth_user_id = user_id_mapping[email] + unless fusionauth_user_id + puts " > Warning: User ID not found for #{email}. Skipping link." + next + end # Create the link link_request = { 'identityProviderId' => identity_provider_id, 'identityProviderUserId' => oauth_uid, - 'userId' => fusionauth_user_id + 'userId' => fusionauth_user_id, + 'displayName' => email } log_verbose("Linking #{email} (#{provider}) to FusionAuth user #{fusionauth_user_id}") @@ -219,7 +208,10 @@ def link_social_accounts(social_users) log_verbose("Successfully linked #{email}") linked_count += 1 else - puts " > Failed to link #{email}: #{response.error_response}" + puts " > Failed to link #{email}:" + puts " Status: #{response.status}" + puts " Error: #{response.error_response}" if response.error_response + log_verbose("Link request: #{link_request.to_json}") end end @@ -266,6 +258,9 @@ def link_social_accounts(social_users) end emails_seen.add(email) + # Generate user ID if not present + user['id'] ||= SecureRandom.uuid + # Collect social users for later linking if source_system == 'omniauth' && user['data'] && user['data']['oauth_provider'] social_users << user @@ -283,6 +278,12 @@ def link_social_accounts(social_users) puts " > Processing #{valid_users.length} valid users" + # Build user ID mapping for social account linking + user_id_mapping = {} + valid_users.each do |user| + user_id_mapping[user['email']] = user['id'] + end + # Add additional application registrations if specified if options[:appids] puts " > Adding additional application registrations..." @@ -308,7 +309,7 @@ def link_social_accounts(social_users) # Link social accounts if requested and applicable if options[:linksocial] && source_system == 'omniauth' && social_users.any? puts "" - link_social_accounts(social_users) + link_social_accounts(social_users, user_id_mapping) end puts "" From e8e0087f692bf3398172e70cdf383d317a09c0a2 Mon Sep 17 00:00:00 2001 From: koladev Date: Fri, 25 Jul 2025 08:11:04 +0100 Subject: [PATCH 5/8] minor fixes --- .../built-in-auth/export_users_for_fusionauth.rb | 7 +------ rails/export-scripts/devise/export_users_for_fusionauth.rb | 7 +------ 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/rails/export-scripts/built-in-auth/export_users_for_fusionauth.rb b/rails/export-scripts/built-in-auth/export_users_for_fusionauth.rb index 85b7c77..2d48835 100644 --- a/rails/export-scripts/built-in-auth/export_users_for_fusionauth.rb +++ b/rails/export-scripts/built-in-auth/export_users_for_fusionauth.rb @@ -1,8 +1,5 @@ #!/usr/bin/env ruby -# Simple User Export Script for Rails Authentication -# Exports users as plain JSON - require_relative '../config/environment' require 'json' require 'securerandom' @@ -28,9 +25,7 @@ bcrypt_factor = $1.to_i salt_and_hash = $2 - # According to the user, salt is first 22 characters bcrypt_salt = salt_and_hash[0, 22] - # Password is the remaining characters bcrypt_password = salt_and_hash[22..-1] end @@ -42,7 +37,7 @@ encryptionScheme: "bcrypt", factor: bcrypt_factor, salt: bcrypt_salt, - passwordChangeRequired: false, # Add this field like in Akamai example + passwordChangeRequired: false, verified: user.confirmed?, active: user.confirmed?, registrations: [ diff --git a/rails/export-scripts/devise/export_users_for_fusionauth.rb b/rails/export-scripts/devise/export_users_for_fusionauth.rb index fdff395..cd5dfcb 100755 --- a/rails/export-scripts/devise/export_users_for_fusionauth.rb +++ b/rails/export-scripts/devise/export_users_for_fusionauth.rb @@ -1,8 +1,5 @@ #!/usr/bin/env ruby -# Simple User Export Script for Devise Users -# Exports users as plain JSON - require_relative 'config/environment' require 'json' require 'securerandom' @@ -28,9 +25,7 @@ bcrypt_factor = $1.to_i salt_and_hash = $2 - # According to FusionAuth requirements, salt is first 22 characters bcrypt_salt = salt_and_hash[0, 22] - # Password is the remaining characters bcrypt_password = salt_and_hash[22..-1] end @@ -41,7 +36,7 @@ encryptionScheme: "bcrypt", factor: bcrypt_factor, salt: bcrypt_salt, - passwordChangeRequired: false, # Add this field like in Akamai example + passwordChangeRequired: false, verified: user.respond_to?(:confirmed?) ? user.confirmed? : true, active: true, registrations: [ From 5913176d23f29ccf40032ceeeb2fa82e18dc6f87 Mon Sep 17 00:00:00 2001 From: tatenda Date: Sun, 27 Jul 2025 22:22:26 +0200 Subject: [PATCH 6/8] sample omniauth user --- .../export-scripts/omniauth/users_export.json | 234 +----------------- 1 file changed, 1 insertion(+), 233 deletions(-) diff --git a/rails/export-scripts/omniauth/users_export.json b/rails/export-scripts/omniauth/users_export.json index 3a33d60..2e9efd1 100644 --- a/rails/export-scripts/omniauth/users_export.json +++ b/rails/export-scripts/omniauth/users_export.json @@ -28,238 +28,6 @@ "last_sign_in_ip": null, "current_sign_in_ip": null } - }, - { - "email": "marcus.johnson@creativestudio.net", - "username": "marcus.johnson@creativestudio.net", - "fullName": "Marcus Johnson", - "password": "8yuwWM680qlB!", - "verified": true, - "active": true, - "imageUrl": "https://via.placeholder.com/80/764ba2/ffffff?text=MJ", - "registrations": [ - { - "id": "1f2980fc-7ac4-49a4-8686-33fec97dd757", - "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", - "verified": true, - "roles": [ - "user" - ] - } - ], - "data": { - "source_system": "omniauth", - "original_user_id": 2, - "oauth_provider": "google_oauth2", - "oauth_uid": "109876543210987654321", - "locked_at": null, - "confirmation_token": null, - "last_sign_in_ip": null, - "current_sign_in_ip": null - } - }, - { - "email": "sofia.rodriguez@datainsights.io", - "username": "sofia.rodriguez@datainsights.io", - "fullName": "Sofia Rodriguez", - "password": "lgPNgTIGYzt6!", - "verified": true, - "active": true, - "imageUrl": "https://via.placeholder.com/80/f093fb/ffffff?text=SR", - "registrations": [ - { - "id": "8c6d8346-fe5d-4542-9211-f0e471866d33", - "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", - "verified": true, - "roles": [ - "user" - ] - } - ], - "data": { - "source_system": "omniauth", - "original_user_id": 3, - "oauth_provider": "google_oauth2", - "oauth_uid": "107123456789012345678", - "locked_at": null, - "confirmation_token": null, - "last_sign_in_ip": null, - "current_sign_in_ip": null - } - }, - { - "email": "chen.wei@startupaccel.cn", - "username": "chen.wei@startupaccel.cn", - "fullName": "Chen Wei", - "password": "7ARgRrd1zryN!", - "verified": true, - "active": true, - "imageUrl": "https://via.placeholder.com/80/4facfe/ffffff?text=CW", - "registrations": [ - { - "id": "fd92c192-428c-45a0-90ac-ef8cbf516978", - "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", - "verified": true, - "roles": [ - "user" - ] - } - ], - "data": { - "source_system": "omniauth", - "original_user_id": 4, - "oauth_provider": "google_oauth2", - "oauth_uid": "106789012345678901234", - "locked_at": null, - "confirmation_token": null, - "last_sign_in_ip": null, - "current_sign_in_ip": null - } - }, - { - "email": "emma.thompson@freelancedesign.co.uk", - "username": "emma.thompson@freelancedesign.co.uk", - "fullName": "Emma Thompson", - "password": "yikMeaYPs9Q6!", - "verified": true, - "active": true, - "imageUrl": "https://via.placeholder.com/80/43e97b/ffffff?text=ET", - "registrations": [ - { - "id": "47cd443f-e0cc-465c-93a9-4cb0f5008206", - "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", - "verified": true, - "roles": [ - "user" - ] - } - ], - "data": { - "source_system": "omniauth", - "original_user_id": 5, - "oauth_provider": "google_oauth2", - "oauth_uid": "105456789012345678901", - "locked_at": null, - "confirmation_token": null, - "last_sign_in_ip": null, - "current_sign_in_ip": null - } - }, - { - "email": "raj.patel@consultingfirm.in", - "username": "raj.patel@consultingfirm.in", - "fullName": "Raj Patel", - "password": "Unq8sPOZJtFV!", - "verified": true, - "active": true, - "imageUrl": "https://via.placeholder.com/80/f6ad55/ffffff?text=RP", - "registrations": [ - { - "id": "d14eb917-d98b-460a-b6ca-662fb327b77f", - "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", - "verified": true, - "roles": [ - "user" - ] - } - ], - "data": { - "source_system": "omniauth", - "original_user_id": 6, - "oauth_provider": "google_oauth2", - "oauth_uid": "104321098765432109876", - "locked_at": null, - "confirmation_token": null, - "last_sign_in_ip": null, - "current_sign_in_ip": null - } - }, - { - "email": "inactive.oauth@oldaccount.com", - "username": "inactive.oauth@oldaccount.com", - "fullName": "Inactive OAuth User", - "password": "RsNPbaBVUwR9!", - "verified": true, - "active": false, - "imageUrl": "https://via.placeholder.com/80/6c757d/ffffff?text=IO", - "registrations": [ - { - "id": "e2696e20-1216-458b-991f-4b9c881eb4bb", - "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", - "verified": true, - "roles": [ - "user" - ] - } - ], - "data": { - "source_system": "omniauth", - "original_user_id": 7, - "oauth_provider": "google_oauth2", - "oauth_uid": "103987654321098765432", - "locked_at": null, - "confirmation_token": null, - "last_sign_in_ip": null, - "current_sign_in_ip": null - } - }, - { - "email": "dev.tester@localhost.dev", - "username": "dev.tester@localhost.dev", - "fullName": "Development Tester", - "password": "6bIv8xDX67uV!", - "verified": true, - "active": true, - "imageUrl": "https://via.placeholder.com/80/28a745/ffffff?text=DEV", - "registrations": [ - { - "id": "815b74f9-e3f5-47d3-b7de-68300273de2e", - "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", - "verified": true, - "roles": [ - "user" - ] - } - ], - "data": { - "source_system": "omniauth", - "original_user_id": 8, - "oauth_provider": "developer", - "oauth_uid": "dev.tester@localhost.dev", - "locked_at": null, - "confirmation_token": null, - "last_sign_in_ip": null, - "current_sign_in_ip": null - } - }, - { - "email": "qa.engineer@testingteam.local", - "username": "qa.engineer@testingteam.local", - "fullName": "QA Engineer", - "password": "7zioKbQLhYs6!", - "verified": true, - "active": true, - "imageUrl": "https://via.placeholder.com/80/17a2b8/ffffff?text=QA", - "registrations": [ - { - "id": "ea0f348a-1803-475a-987d-7f062f087825", - "applicationId": "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e", - "verified": true, - "roles": [ - "user" - ] - } - ], - "data": { - "source_system": "omniauth", - "original_user_id": 9, - "oauth_provider": "developer", - "oauth_uid": "qa.engineer@testingteam.local", - "locked_at": null, - "confirmation_token": null, - "last_sign_in_ip": null, - "current_sign_in_ip": null - } } ] -} \ No newline at end of file +} From e39aa0a5c5533134c7d743391e218530b3cf07f8 Mon Sep 17 00:00:00 2001 From: Sarah McGregor Date: Wed, 30 Jul 2025 12:12:30 +0200 Subject: [PATCH 7/8] Edit: FusionAuth Import Script For Ruby On Rails --- rails/README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/rails/README.md b/rails/README.md index 8941a76..fed250e 100644 --- a/rails/README.md +++ b/rails/README.md @@ -1,13 +1,13 @@ -# FusionAuth Import Script for Ruby on Rails +# FusionAuth Import Script For Ruby On Rails -A script to import user data from Rails authentication systems into FusionAuth. Features duplicate handling, verbose logging, and social account linking for OmniAuth integrations. +A script to import user data from Rails-based authentication systems into FusionAuth. Features duplicate handling, verbose logging, and social account linking for OmniAuth integrations. ## Prerequisites -1. **FusionAuth Instance**: Running FusionAuth server (default: ) -2. **API Key**: FusionAuth API key with user import permissions -3. **Ruby**: Ruby 2.7+ with bundler -4. **Export File**: JSON export file from your Rails auth system. Refer to the [FusionAuth documentation](https://fusionauth.io/docs/apis/users#import-users) for details. +1. **FusionAuth Instance:** Running FusionAuth server (default: ) +2. **API Key:** FusionAuth API key with user import permissions +3. **Ruby:** Ruby 2.7 or higher with bundler +4. **Export File:** JSON export file from your Rails-based authentication system. Refer to the [FusionAuth Users documentation](https://fusionauth.io/docs/apis/users#import-users) for details. ## Installation @@ -58,17 +58,17 @@ This imports users from the file into FusionAuth's default tenant and applicatio | `--users-file` | `-u` | No | `users.json` | Path to the exported JSON user file | | `--fusionauth-api-key` | `-k` | **Yes** | - | FusionAuth API key | | `--fusionauth-url` | `-f` | No | `http://localhost:9011` | FusionAuth instance URL | -| `--fusionauth-tenant-id` | `-t` | No* | - | Tenant ID (required if multiple tenants exist) | -| `--source-system` | `-s` | No | Auto-detected | Source system: devise, rails_auth, omniauth | -| `--register-users` | `-r` | No | - | Comma-separated list of application IDs | +| `--fusionauth-tenant-id` | `-t` | No | - | Tenant Id (required if multiple tenants exist) | +| `--source-system` | `-s` | No | Auto-detected | Source system: `devise`, `rails_auth`, or `omniauth` | +| `--register-users` | `-r` | No | - | Comma-separated list of application Ids | | `--link-social-accounts` | `-l` | No | `false` | Link social accounts for OmniAuth users | | `--verbose` | `-v` | No | `false` | Enable detailed logging | | `--help` | `-h` | No | - | Show help message | ## Supported Authentication Systems -**Devise**: Imports users, encrypted passwords, and user metadata. Preserves email confirmation status and account locking. +**Devise:** Imports users, encrypted passwords, and user metadata. Preserves email confirmation status and account locking. -**Rails Auth**: Imports users, confirmation status, and sign-in tracking data. +**OmniAuth:** Imports social accounts with identity provider linking. Specify social provider and provider user Id in the `user[x].data` field. -**OmniAuth**: Imports social accounts with identity provider linking. Specify social provider and provider user ID in the `user[x].data` field. +**Rails In-Built Authentication:** Imports users, confirmation status, and sign-in tracking data. From 12db528c455dbda5a92ea4a9884fff088e8f6512 Mon Sep 17 00:00:00 2001 From: Sarah McGregor Date: Wed, 30 Jul 2025 12:24:56 +0200 Subject: [PATCH 8/8] Punctuation fix --- rails/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rails/README.md b/rails/README.md index fed250e..83aeda4 100644 --- a/rails/README.md +++ b/rails/README.md @@ -11,19 +11,19 @@ A script to import user data from Rails-based authentication systems into Fusion ## Installation -1. Install dependencies: +1. Install dependencies. ```bash bundle install ``` -2. Make the script executable: +2. Make the script executable. ```bash chmod +x import.rb ``` -3. (Optional) Make the wrapper script executable: +3. (Optional) Make the wrapper script executable. ```bash chmod +x import.sh