From 013b16774ed9bbd34e09c8be1a43ba9e7a932eba Mon Sep 17 00:00:00 2001
From: bivanalhar <bivan.alzacky@gmail.com>
Date: Fri, 27 Dec 2024 14:31:35 +0800
Subject: [PATCH 1/2] feat(keycloak): use CM userId for keycloak user ID

---
 authentication/import/coursemology_realm.json | 20 +++++++++----------
 authentication/script/cm_db_federation.sql    | 10 +++++-----
 2 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/authentication/import/coursemology_realm.json b/authentication/import/coursemology_realm.json
index 9714c2af38c..8fcf23b203d 100644
--- a/authentication/import/coursemology_realm.json
+++ b/authentication/import/coursemology_realm.json
@@ -1800,22 +1800,22 @@
             "false"
           ],
           "findBySearchTerm": [
-            "select ue.id as \"id\", users.id as \"user_id\", ue.email as \"username\", ue.email as \"email\", users.name as \"firstName\", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as \"EMAIL_VERIFIED\"from user_emails ue left join users on ue.user_id = users.id where LOWER(ue.email) like LOWER(concat('%', ?, '%')) or LOWER(users.name) like LOWER(concat('%', ?, '%'))"
+              "SELECT users.id AS \"id\", users.id AS \"user_id\", pe.email AS \"username\", pe.email AS \"email\", users.name AS \"firstName\", CASE WHEN MAX(CASE WHEN pe.confirmed_at IS NOT NULL THEN 1 ELSE 0 END) = 1 THEN 'TRUE' ELSE NULL END AS \"EMAIL_VERIFIED\" FROM user_emails ue LEFT JOIN users ON ue.user_id = users.id LEFT JOIN user_emails pe ON ue.user_id = pe.user_id AND pe.primary = TRUE WHERE LOWER(pe.email) LIKE LOWER(CONCAT('%', ?, '%')) OR LOWER(users.name) LIKE LOWER(CONCAT('%', ?, '%')) OR (ue.primary = FALSE AND ue.confirmed_at IS NOT NULL AND LOWER(ue.email) LIKE LOWER(CONCAT('%', ?, '%'))) GROUP BY pe.id, users.id, pe.email, users.name"
           ],
           "password": [
             "password"
           ],
           "findByUsername": [
-            "select ue.id as \"id\", users.id as \"user_id\", ue.email as \"username\", ue.email as \"email\", users.name as \"firstName\", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as \"EMAIL_VERIFIED\" from user_emails ue left join users on ue.user_id = users.id where LOWER(ue.email) = LOWER(?)"
+              "SELECT users.id as \"id\", users.id as \"user_id\", pe.email as \"username\", pe.email as \"email\", users.name as \"firstName\", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as \"EMAIL_VERIFIED\" from user_emails ue left join users on ue.user_id = users.id left join user_emails pe on ue.user_id = pe.user_id AND pe.primary = TRUE where LOWER(ue.email) = LOWER(?) AND ue.confirmed_at IS NOT NULL GROUP BY pe.id, users.id, pe.email, users.name, ue.confirmed_at"
           ],
           "findById": [
-            "select ue.id as \"id\", users.id as \"user_id\", ue.email as \"username\", ue.email as \"email\", users.name as \"firstName\", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as \"EMAIL_VERIFIED\" from user_emails ue left join users on ue.user_id = users.id where cast(ue.id as character varying) = ?"
+              "SELECT users.id as \"id\", users.id as \"user_id\", pe.email as \"username\", pe.email as \"email\", users.name as \"firstName\", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as \"EMAIL_VERIFIED\" from user_emails ue left join users on ue.user_id = users.id left join user_emails pe ON ue.user_id = pe.user_id AND pe.primary = TRUE where cast(users.id as character varying) = ? AND ue.confirmed_at IS NOT NULL GROUP BY pe.id, users.id, pe.email, users.name, ue.confirmed_at"
           ],
           "listAll": [
-            "select ue.id as \"id\", users.id as \"user_id\", ue.email as \"username\", ue.email as \"email\", users.name as \"firstName\", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as \"EMAIL_VERIFIED\" from user_emails ue left join users on ue.user_id = users.id"
+              "SELECT users.id as \"id\", users.id as \"user_id\", pe.email as \"username\", pe.email as \"email\", users.name as \"firstName\", CASE WHEN pe.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as \"EMAIL_VERIFIED\" from user_emails pe left join users on pe.user_id = users.id where pe.primary = TRUE"
           ],
           "findByEmail": [
-            "select ue.id as \"id\", users.id as \"user_id\", ue.email as \"username\", ue.email as \"email\", users.name as \"firstName\", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as \"EMAIL_VERIFIED\" from user_emails ue left join users on ue.user_id = users.id where LOWER(ue.email) = LOWER(?)"
+              "SELECT users.id as \"id\", users.id as \"user_id\", pe.email as \"username\", pe.email as \"email\", users.name as \"firstName\", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as \"EMAIL_VERIFIED\" from user_emails ue left join users on ue.user_id = users.id left join user_emails pe on ue.user_id = pe.user_id AND pe.primary = TRUE where LOWER(ue.email) = LOWER(?) AND ue.confirmed_at IS NOT NULL GROUP BY pe.id, users.id, pe.email, users.name, ue.confirmed_at"
           ],
           "user": [
             "postgres"
@@ -4192,19 +4192,19 @@
             "false"
           ],
           "findBySearchTerm": [
-            "select ue.id as \"id\", users.id as \"user_id\", ue.email as \"username\", ue.email as \"email\", users.name as \"firstName\", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as \"EMAIL_VERIFIED\"from user_emails ue left join users on ue.user_id = users.id where LOWER(ue.email) like LOWER(concat('%', ?, '%')) or LOWER(users.name) like LOWER(concat('%', ?, '%'))"
+              "SELECT users.id AS \"id\", users.id AS \"user_id\", pe.email AS \"username\", pe.email AS \"email\", users.name AS \"firstName\", CASE WHEN MAX(CASE WHEN pe.confirmed_at IS NOT NULL THEN 1 ELSE 0 END) = 1 THEN 'TRUE' ELSE NULL END AS \"EMAIL_VERIFIED\" FROM user_emails ue LEFT JOIN users ON ue.user_id = users.id LEFT JOIN user_emails pe ON ue.user_id = pe.user_id AND pe.primary = TRUE WHERE LOWER(pe.email) LIKE LOWER(CONCAT('%', ?, '%')) OR LOWER(users.name) LIKE LOWER(CONCAT('%', ?, '%')) OR (ue.primary = FALSE AND ue.confirmed_at IS NOT NULL AND LOWER(ue.email) LIKE LOWER(CONCAT('%', ?, '%'))) GROUP BY pe.id, users.id, pe.email, users.name"
           ],
           "findByUsername": [
-            "select ue.id as \"id\", users.id as \"user_id\", ue.email as \"username\", ue.email as \"email\", users.name as \"firstName\", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as \"EMAIL_VERIFIED\" from user_emails ue left join users on ue.user_id = users.id where LOWER(ue.email) = LOWER(?)"
+              "SELECT users.id as \"id\", users.id as \"user_id\", pe.email as \"username\", pe.email as \"email\", users.name as \"firstName\", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as \"EMAIL_VERIFIED\" from user_emails ue left join users on ue.user_id = users.id left join user_emails pe on ue.user_id = pe.user_id AND pe.primary = TRUE where LOWER(ue.email) = LOWER(?) AND ue.confirmed_at IS NOT NULL GROUP BY pe.id, users.id, pe.email, users.name, ue.confirmed_at"
           ],
           "findById": [
-            "select ue.id as \"id\", users.id as \"user_id\", ue.email as \"username\", ue.email as \"email\", users.name as \"firstName\", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as \"EMAIL_VERIFIED\" from user_emails ue left join users on ue.user_id = users.id where cast(ue.id as character varying) = ?"
+              "SELECT users.id as \"id\", users.id as \"user_id\", pe.email as \"username\", pe.email as \"email\", users.name as \"firstName\", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as \"EMAIL_VERIFIED\" from user_emails ue left join users on ue.user_id = users.id left join user_emails pe ON ue.user_id = pe.user_id AND pe.primary = TRUE where cast(users.id as character varying) = ? AND ue.confirmed_at IS NOT NULL GROUP BY pe.id, users.id, pe.email, users.name, ue.confirmed_at"
           ],
           "listAll": [
-            "select ue.id as \"id\", users.id as \"user_id\", ue.email as \"username\", ue.email as \"email\", users.name as \"firstName\", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as \"EMAIL_VERIFIED\" from user_emails ue left join users on ue.user_id = users.id"
+              "SELECT users.id as \"id\", users.id as \"user_id\", pe.email as \"username\", pe.email as \"email\", users.name as \"firstName\", CASE WHEN pe.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as \"EMAIL_VERIFIED\" from user_emails pe left join users on pe.user_id = users.id where pe.primary = TRUE"
           ],
           "findByEmail": [
-            "select ue.id as \"id\", users.id as \"user_id\", ue.email as \"username\", ue.email as \"email\", users.name as \"firstName\", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as \"EMAIL_VERIFIED\" from user_emails ue left join users on ue.user_id = users.id where LOWER(ue.email) = LOWER(?)"
+              "SELECT users.id as \"id\", users.id as \"user_id\", pe.email as \"username\", pe.email as \"email\", users.name as \"firstName\", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as \"EMAIL_VERIFIED\" from user_emails ue left join users on ue.user_id = users.id left join user_emails pe on ue.user_id = pe.user_id AND pe.primary = TRUE where LOWER(ue.email) = LOWER(?) AND ue.confirmed_at IS NOT NULL GROUP BY pe.id, users.id, pe.email, users.name, ue.confirmed_at"
           ],
           "user": [
             "postgres"
diff --git a/authentication/script/cm_db_federation.sql b/authentication/script/cm_db_federation.sql
index 8e9b319280c..d8033865ad1 100644
--- a/authentication/script/cm_db_federation.sql
+++ b/authentication/script/cm_db_federation.sql
@@ -2,19 +2,19 @@
 select count(*) from users;
 
 -- List All Users SQL query
-select ue.id as "id", users.id as "user_id", ue.email as "username", ue.email as "email", users.name as "firstName", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as "EMAIL_VERIFIED" from user_emails ue left join users on ue.user_id = users.id
+select users.id as "id", users.id as "user_id", pe.email as "username", pe.email as "email", users.name as "firstName", CASE WHEN pe.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as "EMAIL_VERIFIED" from user_emails pe left join users on pe.user_id = users.id where pe.primary = TRUE
 
 -- Find user by id SQL query
-select ue.id as "id", users.id as "user_id", ue.email as "username", ue.email as "email", users.name as "firstName", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as "EMAIL_VERIFIED" from user_emails ue left join users on ue.user_id = users.id where cast(ue.id as character varying) = ?
+select users.id as "id", users.id as "user_id", pe.email as "username", pe.email as "email", users.name as "firstName", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as "EMAIL_VERIFIED" from user_emails ue left join users on ue.user_id = users.id left join user_emails pe ON ue.user_id = pe.user_id AND pe.primary = TRUE where cast(users.id as character varying) = ? AND ue.confirmed_at IS NOT NULL GROUP BY pe.id, users.id, pe.email, users.name, ue.confirmed_at
 
 -- Find user by username SQL query
-select ue.id as "id", users.id as "user_id", ue.email as "username", ue.email as "email", users.name as "firstName", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as "EMAIL_VERIFIED" from user_emails ue left join users on ue.user_id = users.id where LOWER(ue.email) = LOWER(?)
+select users.id as "id", users.id as "user_id", pe.email as "username", pe.email as "email", users.name as "firstName", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as "EMAIL_VERIFIED" from user_emails ue left join users on ue.user_id = users.id LEFT JOIN user_emails pe ON ue.user_id = pe.user_id AND pe.primary = TRUE where LOWER(ue.email) = LOWER(?) AND ue.confirmed_at IS NOT NULL GROUP BY pe.id, users.id, pe.email, users.name, ue.confirmed_at
 
 -- Find user by email SQL query
-select ue.id as "id", users.id as "user_id", ue.email as "username", ue.email as "email", users.name as "firstName", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as "EMAIL_VERIFIED" from user_emails ue left join users on ue.user_id = users.id where LOWER(ue.email) = LOWER(?)
+select users.id as "id", users.id as "user_id", pe.email as "username", pe.email as "email", users.name as "firstName", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as "EMAIL_VERIFIED" from user_emails ue left join users on ue.user_id = users.id LEFT JOIN user_emails pe ON ue.user_id = pe.user_id AND pe.primary = TRUE where LOWER(ue.email) = LOWER(?) AND ue.confirmed_at IS NOT NULL GROUP BY pe.id, users.id, pe.email, users.name, ue.confirmed_at
 
 -- Find user by search term SQL query
-select ue.id as "id", users.id as "user_id", ue.email as "username", ue.email as "email", users.name as "firstName", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as "EMAIL_VERIFIED"from user_emails ue left join users on ue.user_id = users.id where LOWER(ue.email) like LOWER(concat('%', ?, '%')) or LOWER(users.name) like LOWER(concat('%', ?, '%'))
+SELECT users.id AS "id", users.id AS "user_id", pe.email AS "username", pe.email AS "email", users.name AS "firstName", CASE WHEN MAX(CASE WHEN pe.confirmed_at IS NOT NULL THEN 1 ELSE 0 END) = 1 THEN 'TRUE' ELSE NULL END AS "EMAIL_VERIFIED" FROM user_emails ue LEFT JOIN users ON ue.user_id = users.id LEFT JOIN user_emails pe ON ue.user_id = pe.user_id AND pe.primary = TRUE WHERE LOWER(pe.email) LIKE LOWER(CONCAT('%', ?, '%')) OR LOWER(users.name) LIKE LOWER(CONCAT('%', ?, '%')) OR (ue.primary = FALSE AND ue.confirmed_at IS NOT NULL AND LOWER(ue.email) LIKE LOWER(CONCAT('%', ?, '%'))) GROUP BY pe.id, users.id, pe.email, users.name
 
 -- Find password hash (blowfish or hash digest hex) SQL query
 select encrypted_password as hash_pwd from users right join user_emails ue on users.id = ue.user_id where ue.email = ?

From e62be73c1a9bda24e256ecb09f527e3ff0af5a79 Mon Sep 17 00:00:00 2001
From: bivanalhar <bivan.alzacky@gmail.com>
Date: Fri, 27 Dec 2024 14:31:49 +0800
Subject: [PATCH 2/2] feat(user): prevent destroying primary email

---
 app/controllers/user/emails_controller.rb     |  7 ++++++
 authentication/import/coursemology_realm.json | 14 +++++------
 authentication/script/cm_db_federation.sql    | 10 ++++----
 config/locales/en/user/emails.yml             |  1 +
 config/locales/ko/user/emails.yml             |  1 +
 config/locales/zh/user/emails.yml             |  1 +
 .../user/emails_controller_spec.rb            | 25 +++++++++++++------
 7 files changed, 39 insertions(+), 20 deletions(-)

diff --git a/app/controllers/user/emails_controller.rb b/app/controllers/user/emails_controller.rb
index e2a125bded2..985caa4bb9f 100644
--- a/app/controllers/user/emails_controller.rb
+++ b/app/controllers/user/emails_controller.rb
@@ -14,6 +14,13 @@ def create
   end
 
   def destroy
+    if @email.primary?
+      @email.errors.add(:base, I18n.t('user.emails.index.cannot_delete_primary'))
+      render json: { errors: @email.errors.full_messages.to_sentence }, status: :bad_request
+
+      return
+    end
+
     if @email.destroy
       render_emails
     else
diff --git a/authentication/import/coursemology_realm.json b/authentication/import/coursemology_realm.json
index 8fcf23b203d..f5da68b0ccd 100644
--- a/authentication/import/coursemology_realm.json
+++ b/authentication/import/coursemology_realm.json
@@ -1788,7 +1788,7 @@
             "select encrypted_password as hash_pwd from users right join user_emails ue on users.id = ue.user_id where LOWER(ue.email) = LOWER(?)"
           ],
           "cachePolicy": [
-            "DEFAULT"
+            "NO_CACHE"
           ],
           "url": [
             "jdbc:postgresql://host.docker.internal:5432/coursemology"
@@ -1800,22 +1800,22 @@
             "false"
           ],
           "findBySearchTerm": [
-              "SELECT users.id AS \"id\", users.id AS \"user_id\", pe.email AS \"username\", pe.email AS \"email\", users.name AS \"firstName\", CASE WHEN MAX(CASE WHEN pe.confirmed_at IS NOT NULL THEN 1 ELSE 0 END) = 1 THEN 'TRUE' ELSE NULL END AS \"EMAIL_VERIFIED\" FROM user_emails ue LEFT JOIN users ON ue.user_id = users.id LEFT JOIN user_emails pe ON ue.user_id = pe.user_id AND pe.primary = TRUE WHERE LOWER(pe.email) LIKE LOWER(CONCAT('%', ?, '%')) OR LOWER(users.name) LIKE LOWER(CONCAT('%', ?, '%')) OR (ue.primary = FALSE AND ue.confirmed_at IS NOT NULL AND LOWER(ue.email) LIKE LOWER(CONCAT('%', ?, '%'))) GROUP BY pe.id, users.id, pe.email, users.name"
+              "SELECT users.id AS \"id\", users.id AS \"user_id\", pe.email AS \"username\", pe.email AS \"email\", users.name AS \"firstName\", CASE WHEN MAX(CASE WHEN pe.confirmed_at IS NOT NULL THEN 1 ELSE 0 END) = 1 THEN 'TRUE' ELSE 'FALSE' END AS \"EMAIL_VERIFIED\" FROM user_emails ue LEFT JOIN users ON ue.user_id = users.id LEFT JOIN user_emails pe ON ue.user_id = pe.user_id AND pe.primary = TRUE WHERE LOWER(pe.email) LIKE LOWER(CONCAT('%', ?, '%')) OR LOWER(users.name) LIKE LOWER(CONCAT('%', ?, '%')) OR (ue.primary = FALSE AND ue.confirmed_at IS NOT NULL AND LOWER(ue.email) LIKE LOWER(CONCAT('%', ?, '%'))) GROUP BY pe.id, users.id, pe.email, users.name"
           ],
           "password": [
             "password"
           ],
           "findByUsername": [
-              "SELECT users.id as \"id\", users.id as \"user_id\", pe.email as \"username\", pe.email as \"email\", users.name as \"firstName\", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as \"EMAIL_VERIFIED\" from user_emails ue left join users on ue.user_id = users.id left join user_emails pe on ue.user_id = pe.user_id AND pe.primary = TRUE where LOWER(ue.email) = LOWER(?) AND ue.confirmed_at IS NOT NULL GROUP BY pe.id, users.id, pe.email, users.name, ue.confirmed_at"
+              "SELECT users.id as \"id\", users.id as \"user_id\", ue.email as \"username\", ue.email as \"email\", users.name as \"firstName\", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE 'FALSE' END as \"EMAIL_VERIFIED\" from user_emails ue left join users on ue.user_id = users.id where LOWER(ue.email) = LOWER(?) GROUP BY users.id, ue.email, users.name, ue.confirmed_at"
           ],
           "findById": [
-              "SELECT users.id as \"id\", users.id as \"user_id\", pe.email as \"username\", pe.email as \"email\", users.name as \"firstName\", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as \"EMAIL_VERIFIED\" from user_emails ue left join users on ue.user_id = users.id left join user_emails pe ON ue.user_id = pe.user_id AND pe.primary = TRUE where cast(users.id as character varying) = ? AND ue.confirmed_at IS NOT NULL GROUP BY pe.id, users.id, pe.email, users.name, ue.confirmed_at"
+              "SELECT users.id as \"id\", users.id as \"user_id\", pe.email as \"username\", pe.email as \"email\", users.name as \"firstName\" from user_emails pe left join users on pe.user_id = users.id where cast(users.id as character varying) = ? AND pe.primary = TRUE AND pe.confirmed_at IS NOT NULL GROUP BY users.id, pe.email, users.name"
           ],
           "listAll": [
-              "SELECT users.id as \"id\", users.id as \"user_id\", pe.email as \"username\", pe.email as \"email\", users.name as \"firstName\", CASE WHEN pe.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as \"EMAIL_VERIFIED\" from user_emails pe left join users on pe.user_id = users.id where pe.primary = TRUE"
+              "SELECT users.id as \"id\", users.id as \"user_id\", pe.email as \"username\", pe.email as \"email\", users.name as \"firstName\", CASE WHEN pe.confirmed_at IS NOT NULL THEN 'TRUE' ELSE 'FALSE' END as \"EMAIL_VERIFIED\" from user_emails pe left join users on pe.user_id = users.id where pe.primary = TRUE"
           ],
           "findByEmail": [
-              "SELECT users.id as \"id\", users.id as \"user_id\", pe.email as \"username\", pe.email as \"email\", users.name as \"firstName\", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as \"EMAIL_VERIFIED\" from user_emails ue left join users on ue.user_id = users.id left join user_emails pe on ue.user_id = pe.user_id AND pe.primary = TRUE where LOWER(ue.email) = LOWER(?) AND ue.confirmed_at IS NOT NULL GROUP BY pe.id, users.id, pe.email, users.name, ue.confirmed_at"
+              "SELECT users.id as \"id\", users.id as \"user_id\", ue.email as \"username\", ue.email as \"email\", users.name as \"firstName\", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE 'FALSE' END as \"EMAIL_VERIFIED\" from user_emails ue left join users on ue.user_id = users.id where LOWER(ue.email) = LOWER(?) GROUP BY users.id, ue.email, users.name, ue.confirmed_at"
           ],
           "user": [
             "postgres"
@@ -4180,7 +4180,7 @@
             "select count(*) from users"
           ],
           "cachePolicy": [
-            "DEFAULT"
+            "NO_CACHE"
           ],
           "url": [
             "jdbc:postgresql://host.docker.internal:5432/coursemology_test"
diff --git a/authentication/script/cm_db_federation.sql b/authentication/script/cm_db_federation.sql
index d8033865ad1..de2bc9717ee 100644
--- a/authentication/script/cm_db_federation.sql
+++ b/authentication/script/cm_db_federation.sql
@@ -2,19 +2,19 @@
 select count(*) from users;
 
 -- List All Users SQL query
-select users.id as "id", users.id as "user_id", pe.email as "username", pe.email as "email", users.name as "firstName", CASE WHEN pe.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as "EMAIL_VERIFIED" from user_emails pe left join users on pe.user_id = users.id where pe.primary = TRUE
+select users.id as "id", users.id as "user_id", pe.email as "username", pe.email as "email", users.name as "firstName", CASE WHEN pe.confirmed_at IS NOT NULL THEN 'TRUE' ELSE 'FALSE' END as "EMAIL_VERIFIED" from user_emails pe left join users on pe.user_id = users.id where pe.primary = TRUE
 
 -- Find user by id SQL query
-select users.id as "id", users.id as "user_id", pe.email as "username", pe.email as "email", users.name as "firstName", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as "EMAIL_VERIFIED" from user_emails ue left join users on ue.user_id = users.id left join user_emails pe ON ue.user_id = pe.user_id AND pe.primary = TRUE where cast(users.id as character varying) = ? AND ue.confirmed_at IS NOT NULL GROUP BY pe.id, users.id, pe.email, users.name, ue.confirmed_at
+select users.id as "id", users.id as "user_id", pe.email as "username", pe.email as "email", users.name as "firstName" from user_emails pe left join users on pe.user_id = users.id where cast(users.id as character varying) = ? AND pe.primary = TRUE and pe.confirmed_at IS NOT NULL GROUP BY users.id, pe.email, users.name
 
 -- Find user by username SQL query
-select users.id as "id", users.id as "user_id", pe.email as "username", pe.email as "email", users.name as "firstName", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as "EMAIL_VERIFIED" from user_emails ue left join users on ue.user_id = users.id LEFT JOIN user_emails pe ON ue.user_id = pe.user_id AND pe.primary = TRUE where LOWER(ue.email) = LOWER(?) AND ue.confirmed_at IS NOT NULL GROUP BY pe.id, users.id, pe.email, users.name, ue.confirmed_at
+select users.id as "id", users.id as "user_id", ue.email as "username", ue.email as "email", users.name as "firstName", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE 'FALSE' END as "EMAIL_VERIFIED" from user_emails ue left join users on ue.user_id = users.id where LOWER(ue.email) = LOWER(?) GROUP BY users.id, ue.email, users.name, ue.confirmed_at
 
 -- Find user by email SQL query
-select users.id as "id", users.id as "user_id", pe.email as "username", pe.email as "email", users.name as "firstName", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE NULL END as "EMAIL_VERIFIED" from user_emails ue left join users on ue.user_id = users.id LEFT JOIN user_emails pe ON ue.user_id = pe.user_id AND pe.primary = TRUE where LOWER(ue.email) = LOWER(?) AND ue.confirmed_at IS NOT NULL GROUP BY pe.id, users.id, pe.email, users.name, ue.confirmed_at
+select users.id as "id", users.id as "user_id", ue.email as "username", ue.email as "email", users.name as "firstName", CASE WHEN ue.confirmed_at IS NOT NULL THEN 'TRUE' ELSE 'FALSE' END as "EMAIL_VERIFIED" from user_emails ue left join users on ue.user_id = users.id where LOWER(ue.email) = LOWER(?) GROUP BY users.id, ue.email, users.name, ue.confirmed_at
 
 -- Find user by search term SQL query
-SELECT users.id AS "id", users.id AS "user_id", pe.email AS "username", pe.email AS "email", users.name AS "firstName", CASE WHEN MAX(CASE WHEN pe.confirmed_at IS NOT NULL THEN 1 ELSE 0 END) = 1 THEN 'TRUE' ELSE NULL END AS "EMAIL_VERIFIED" FROM user_emails ue LEFT JOIN users ON ue.user_id = users.id LEFT JOIN user_emails pe ON ue.user_id = pe.user_id AND pe.primary = TRUE WHERE LOWER(pe.email) LIKE LOWER(CONCAT('%', ?, '%')) OR LOWER(users.name) LIKE LOWER(CONCAT('%', ?, '%')) OR (ue.primary = FALSE AND ue.confirmed_at IS NOT NULL AND LOWER(ue.email) LIKE LOWER(CONCAT('%', ?, '%'))) GROUP BY pe.id, users.id, pe.email, users.name
+SELECT users.id AS "id", users.id AS "user_id", pe.email AS "username", pe.email AS "email", users.name AS "firstName", CASE WHEN MAX(CASE WHEN pe.confirmed_at IS NOT NULL THEN 1 ELSE 0 END) = 1 THEN 'TRUE' ELSE 'FALSE' END AS "EMAIL_VERIFIED" FROM user_emails ue LEFT JOIN users ON ue.user_id = users.id LEFT JOIN user_emails pe ON ue.user_id = pe.user_id AND pe.primary = TRUE WHERE LOWER(pe.email) LIKE LOWER(CONCAT('%', ?, '%')) OR LOWER(users.name) LIKE LOWER(CONCAT('%', ?, '%')) OR (ue.primary = FALSE AND ue.confirmed_at IS NOT NULL AND LOWER(ue.email) LIKE LOWER(CONCAT('%', ?, '%'))) GROUP BY pe.id, users.id, pe.email, users.name
 
 -- Find password hash (blowfish or hash digest hex) SQL query
 select encrypted_password as hash_pwd from users right join user_emails ue on users.id = ue.user_id where ue.email = ?
diff --git a/config/locales/en/user/emails.yml b/config/locales/en/user/emails.yml
index 5e7a2469847..d298d5648fe 100644
--- a/config/locales/en/user/emails.yml
+++ b/config/locales/en/user/emails.yml
@@ -3,6 +3,7 @@ en:
     emails:
       index:
         header: 'Emails'
+        cannot_delete_primary: 'You cannot delete the primary email.'
       set_primary:
         no_confirmed_emails: 'There are no confirmed emails to set as the primary email.'
       send_confirmation:
diff --git a/config/locales/ko/user/emails.yml b/config/locales/ko/user/emails.yml
index 7dab3b9c37a..53139d09eea 100644
--- a/config/locales/ko/user/emails.yml
+++ b/config/locales/ko/user/emails.yml
@@ -3,6 +3,7 @@ ko:
     emails:
       index:
         header: '이메일'
+        cannot_delete_primary: '기본 이메일을 삭제할 수 없습니다.'
       set_primary:
         no_confirmed_emails: '기본 이메일로 설정할 수 있는 확인된 이메일 주소가 없습니다.'
       send_confirmation:
diff --git a/config/locales/zh/user/emails.yml b/config/locales/zh/user/emails.yml
index 5f774cabe34..86b0e12708e 100644
--- a/config/locales/zh/user/emails.yml
+++ b/config/locales/zh/user/emails.yml
@@ -3,6 +3,7 @@ zh:
     emails:
       index:
         header: '邮箱'
+        cannot_delete_primary: '不能删除主要邮箱。'
       set_primary:
         no_confirmed_emails: '没有已验证的邮箱地址可以设置为主要邮箱。'
       send_confirmation:
diff --git a/spec/controllers/user/emails_controller_spec.rb b/spec/controllers/user/emails_controller_spec.rb
index 4ce17138014..3bae3a97b34 100644
--- a/spec/controllers/user/emails_controller_spec.rb
+++ b/spec/controllers/user/emails_controller_spec.rb
@@ -6,10 +6,12 @@
 
   with_tenant(:instance) do
     let!(:user) { create(:administrator) }
+    let!(:primary_email) { user.send(:default_email_record) }
+    let!(:non_primary_email) { create(:user_email, user: user, primary: false) }
     before { controller_sign_in(controller, user) }
 
-    describe '#destroy' do
-      subject { delete :destroy, as: :json, params: { id: user.send(:default_email_record) } }
+    describe '#destroy_primary_email' do
+      subject { delete :destroy, as: :json, params: { id: primary_email.id } }
 
       context 'when the user only has one email address' do
         it 'cannot be deleted' do
@@ -19,16 +21,23 @@
       end
 
       context 'when destroying a primary email' do
-        let!(:non_primary_email) { create(:user_email, user: user, primary: false) }
         before { controller_sign_in(controller, user) }
 
-        it 'deletes the primary email' do
-          expect { subject }.to change { user.emails.count }.by(-1)
+        it 'cannot be deleted' do
+          expect { subject }.to change { user.emails.count }.by(0)
         end
+        it { is_expected.to have_http_status(:bad_request) }
+      end
+    end
 
-        it 'sets another email as primary' do
-          subject
-          expect(non_primary_email.reload).to be_primary
+    describe '#destroy_non_primary_email' do
+      subject { delete :destroy, as: :json, params: { id: non_primary_email.id } }
+
+      context 'when destroying a non-primary email' do
+        before { controller_sign_in(controller, user) }
+
+        it 'deletes that email' do
+          expect { subject }.to change { user.emails.count }.by(-1)
         end
       end
     end