Add 30 and 60 day notifications for expiring access tokens
What does this MR do and why?
Add 30 and 60 day notifications for expiring access tokens
In addition to the current 7 day notifications, this change will add 30 and 60 day email notifications for expiring personal access tokens. This also applies to notifications to owners for project and group access tokens.
For now, webhooks will only issue 7 day notifications, since currently there is not an easy way for users to determine from the payload if a notification is for 7, 30, or 60 days.
- Related to #464040 (closed)
- Part 3 of breaking !163818 (closed) into smaller MRs
- Will need rebase after Part 2 !165592 (merged) is merged
- Part 1 !165257 (merged)
MR acceptance checklist
Please evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Database Review
Get user PATs (seven days interval)
SELECT "personal_access_tokens"."user_id",
"personal_access_tokens"."expires_at",
"personal_access_tokens"."id" AS t0_r0,
"users"."id" AS t1_r0,
"users"."email" AS t1_r1,
"users"."encrypted_password" AS t1_r2,
"users"."reset_password_token" AS t1_r3,
"users"."reset_password_sent_at" AS t1_r4,
"users"."remember_created_at" AS t1_r5,
"users"."sign_in_count" AS t1_r6,
"users"."current_sign_in_at" AS t1_r7,
"users"."last_sign_in_at" AS t1_r8,
"users"."current_sign_in_ip" AS t1_r9,
"users"."last_sign_in_ip" AS t1_r10,
"users"."created_at" AS t1_r11,
"users"."updated_at" AS t1_r12,
"users"."name" AS t1_r13,
"users"."admin" AS t1_r14,
"users"."projects_limit" AS t1_r15,
"users"."failed_attempts" AS t1_r16,
"users"."locked_at" AS t1_r17,
"users"."username" AS t1_r18,
"users"."can_create_group" AS t1_r19,
"users"."can_create_team" AS t1_r20,
"users"."state" AS t1_r21,
"users"."color_scheme_id" AS t1_r22,
"users"."password_expires_at" AS t1_r23,
"users"."created_by_id" AS t1_r24,
"users"."last_credential_check_at" AS t1_r25,
"users"."avatar" AS t1_r26,
"users"."confirmation_token" AS t1_r27,
"users"."confirmed_at" AS t1_r28,
"users"."confirmation_sent_at" AS t1_r29,
"users"."unconfirmed_email" AS t1_r30,
"users"."hide_no_ssh_key" AS t1_r31,
"users"."admin_email_unsubscribed_at" AS t1_r32,
"users"."notification_email" AS t1_r33,
"users"."hide_no_password" AS t1_r34,
"users"."password_automatically_set" AS t1_r35,
"users"."encrypted_otp_secret" AS t1_r36,
"users"."encrypted_otp_secret_iv" AS t1_r37,
"users"."encrypted_otp_secret_salt" AS t1_r38,
"users"."otp_required_for_login" AS t1_r39,
"users"."otp_backup_codes" AS t1_r40,
"users"."public_email" AS t1_r41,
"users"."dashboard" AS t1_r42,
"users"."project_view" AS t1_r43,
"users"."consumed_timestep" AS t1_r44,
"users"."layout" AS t1_r45,
"users"."hide_project_limit" AS t1_r46,
"users"."note" AS t1_r47,
"users"."unlock_token" AS t1_r48,
"users"."otp_grace_period_started_at" AS t1_r49,
"users"."external" AS t1_r50,
"users"."incoming_email_token" AS t1_r51,
"users"."auditor" AS t1_r52,
"users"."require_two_factor_authentication_from_group" AS t1_r53,
"users"."two_factor_grace_period" AS t1_r54,
"users"."last_activity_on" AS t1_r55,
"users"."notified_of_own_activity" AS t1_r56,
"users"."preferred_language" AS t1_r57,
"users"."theme_id" AS t1_r58,
"users"."accepted_term_id" AS t1_r59,
"users"."feed_token" AS t1_r60,
"users"."private_profile" AS t1_r61,
"users"."roadmap_layout" AS t1_r62,
"users"."include_private_contributions" AS t1_r63,
"users"."commit_email" AS t1_r64,
"users"."group_view" AS t1_r65,
"users"."managing_group_id" AS t1_r66,
"users"."first_name" AS t1_r67,
"users"."last_name" AS t1_r68,
"users"."static_object_token" AS t1_r69,
"users"."role" AS t1_r70,
"users"."user_type" AS t1_r71,
"users"."static_object_token_encrypted" AS t1_r72,
"users"."otp_secret_expires_at" AS t1_r73,
"users"."onboarding_in_progress" AS t1_r74,
"users"."color_mode_id" AS t1_r75,
"users"."last_access_from_pipl_country_at" AS t1_r76
FROM "personal_access_tokens"
LEFT OUTER JOIN "users" ON "users"."id" = "personal_access_tokens"."user_id"
WHERE (revoked = FALSE
AND seven_days_notification_sent_at IS NULL
AND expire_notification_delivered = FALSE
AND expires_at BETWEEN '2024-10-11' AND '2024-10-18')
AND "personal_access_tokens"."impersonation" = FALSE
AND "users"."user_type" = 0
ORDER BY "personal_access_tokens"."expires_at" ASC LIMIT 100
Get user PATs (thirty day interval)
SELECT "personal_access_tokens"."user_id",
"personal_access_tokens"."expires_at",
"personal_access_tokens"."id" AS t0_r0,
"users"."id" AS t1_r0,
"users"."email" AS t1_r1,
"users"."encrypted_password" AS t1_r2,
"users"."reset_password_token" AS t1_r3,
"users"."reset_password_sent_at" AS t1_r4,
"users"."remember_created_at" AS t1_r5,
"users"."sign_in_count" AS t1_r6,
"users"."current_sign_in_at" AS t1_r7,
"users"."last_sign_in_at" AS t1_r8,
"users"."current_sign_in_ip" AS t1_r9,
"users"."last_sign_in_ip" AS t1_r10,
"users"."created_at" AS t1_r11,
"users"."updated_at" AS t1_r12,
"users"."name" AS t1_r13,
"users"."admin" AS t1_r14,
"users"."projects_limit" AS t1_r15,
"users"."failed_attempts" AS t1_r16,
"users"."locked_at" AS t1_r17,
"users"."username" AS t1_r18,
"users"."can_create_group" AS t1_r19,
"users"."can_create_team" AS t1_r20,
"users"."state" AS t1_r21,
"users"."color_scheme_id" AS t1_r22,
"users"."password_expires_at" AS t1_r23,
"users"."created_by_id" AS t1_r24,
"users"."last_credential_check_at" AS t1_r25,
"users"."avatar" AS t1_r26,
"users"."confirmation_token" AS t1_r27,
"users"."confirmed_at" AS t1_r28,
"users"."confirmation_sent_at" AS t1_r29,
"users"."unconfirmed_email" AS t1_r30,
"users"."hide_no_ssh_key" AS t1_r31,
"users"."admin_email_unsubscribed_at" AS t1_r32,
"users"."notification_email" AS t1_r33,
"users"."hide_no_password" AS t1_r34,
"users"."password_automatically_set" AS t1_r35,
"users"."encrypted_otp_secret" AS t1_r36,
"users"."encrypted_otp_secret_iv" AS t1_r37,
"users"."encrypted_otp_secret_salt" AS t1_r38,
"users"."otp_required_for_login" AS t1_r39,
"users"."otp_backup_codes" AS t1_r40,
"users"."public_email" AS t1_r41,
"users"."dashboard" AS t1_r42,
"users"."project_view" AS t1_r43,
"users"."consumed_timestep" AS t1_r44,
"users"."layout" AS t1_r45,
"users"."hide_project_limit" AS t1_r46,
"users"."note" AS t1_r47,
"users"."unlock_token" AS t1_r48,
"users"."otp_grace_period_started_at" AS t1_r49,
"users"."external" AS t1_r50,
"users"."incoming_email_token" AS t1_r51,
"users"."auditor" AS t1_r52,
"users"."require_two_factor_authentication_from_group" AS t1_r53,
"users"."two_factor_grace_period" AS t1_r54,
"users"."last_activity_on" AS t1_r55,
"users"."notified_of_own_activity" AS t1_r56,
"users"."preferred_language" AS t1_r57,
"users"."theme_id" AS t1_r58,
"users"."accepted_term_id" AS t1_r59,
"users"."feed_token" AS t1_r60,
"users"."private_profile" AS t1_r61,
"users"."roadmap_layout" AS t1_r62,
"users"."include_private_contributions" AS t1_r63,
"users"."commit_email" AS t1_r64,
"users"."group_view" AS t1_r65,
"users"."managing_group_id" AS t1_r66,
"users"."first_name" AS t1_r67,
"users"."last_name" AS t1_r68,
"users"."static_object_token" AS t1_r69,
"users"."role" AS t1_r70,
"users"."user_type" AS t1_r71,
"users"."static_object_token_encrypted" AS t1_r72,
"users"."otp_secret_expires_at" AS t1_r73,
"users"."onboarding_in_progress" AS t1_r74,
"users"."color_mode_id" AS t1_r75,
"users"."last_access_from_pipl_country_at" AS t1_r76
FROM "personal_access_tokens"
LEFT OUTER JOIN "users" ON "users"."id" = "personal_access_tokens"."user_id"
WHERE (revoked = FALSE
AND thirty_days_notification_sent_at IS NULL
AND expire_notification_delivered = FALSE
AND expires_at BETWEEN '2024-10-19' AND '2024-11-10')
AND "personal_access_tokens"."impersonation" = FALSE
AND "users"."user_type" = 0
ORDER BY "personal_access_tokens"."expires_at" ASC LIMIT 100
Get token names (seven days interval)
SELECT "personal_access_tokens"."name"
FROM "personal_access_tokens"
WHERE (revoked = FALSE
AND seven_days_notification_sent_at IS NULL
AND expire_notification_delivered = FALSE
AND expires_at BETWEEN '2024-10-11' AND '2024-10-18')
AND "personal_access_tokens"."impersonation" = FALSE
AND "personal_access_tokens"."user_id" = 1 LIMIT 100
Update token notification columns (seven day interval)
UPDATE "personal_access_tokens"
SET "seven_days_notification_sent_at" = '2024-10-11 08:25:44.646546',
"expire_notification_delivered" = TRUE
WHERE (revoked = FALSE
AND seven_days_notification_sent_at IS NULL
AND expire_notification_delivered = FALSE
AND expires_at BETWEEN '2024-10-11' AND '2024-10-18')
AND "personal_access_tokens"."impersonation" = FALSE
AND "personal_access_tokens"."user_id" = 1
Get Project and Group access tokens (seven days interval)
SELECT "personal_access_tokens"."id",
"personal_access_tokens"."user_id",
"personal_access_tokens"."expires_at",
"personal_access_tokens"."id" AS t0_r0,
"users"."id" AS t1_r0,
"users"."email" AS t1_r1,
"users"."encrypted_password" AS t1_r2,
"users"."reset_password_token" AS t1_r3,
"users"."reset_password_sent_at" AS t1_r4,
"users"."remember_created_at" AS t1_r5,
"users"."sign_in_count" AS t1_r6,
"users"."current_sign_in_at" AS t1_r7,
"users"."last_sign_in_at" AS t1_r8,
"users"."current_sign_in_ip" AS t1_r9,
"users"."last_sign_in_ip" AS t1_r10,
"users"."created_at" AS t1_r11,
"users"."updated_at" AS t1_r12,
"users"."name" AS t1_r13,
"users"."admin" AS t1_r14,
"users"."projects_limit" AS t1_r15,
"users"."failed_attempts" AS t1_r16,
"users"."locked_at" AS t1_r17,
"users"."username" AS t1_r18,
"users"."can_create_group" AS t1_r19,
"users"."can_create_team" AS t1_r20,
"users"."state" AS t1_r21,
"users"."color_scheme_id" AS t1_r22,
"users"."password_expires_at" AS t1_r23,
"users"."created_by_id" AS t1_r24,
"users"."last_credential_check_at" AS t1_r25,
"users"."avatar" AS t1_r26,
"users"."confirmation_token" AS t1_r27,
"users"."confirmed_at" AS t1_r28,
"users"."confirmation_sent_at" AS t1_r29,
"users"."unconfirmed_email" AS t1_r30,
"users"."hide_no_ssh_key" AS t1_r31,
"users"."admin_email_unsubscribed_at" AS t1_r32,
"users"."notification_email" AS t1_r33,
"users"."hide_no_password" AS t1_r34,
"users"."password_automatically_set" AS t1_r35,
"users"."encrypted_otp_secret" AS t1_r36,
"users"."encrypted_otp_secret_iv" AS t1_r37,
"users"."encrypted_otp_secret_salt" AS t1_r38,
"users"."otp_required_for_login" AS t1_r39,
"users"."otp_backup_codes" AS t1_r40,
"users"."public_email" AS t1_r41,
"users"."dashboard" AS t1_r42,
"users"."project_view" AS t1_r43,
"users"."consumed_timestep" AS t1_r44,
"users"."layout" AS t1_r45,
"users"."hide_project_limit" AS t1_r46,
"users"."note" AS t1_r47,
"users"."unlock_token" AS t1_r48,
"users"."otp_grace_period_started_at" AS t1_r49,
"users"."external" AS t1_r50,
"users"."incoming_email_token" AS t1_r51,
"users"."auditor" AS t1_r52,
"users"."require_two_factor_authentication_from_group" AS t1_r53,
"users"."two_factor_grace_period" AS t1_r54,
"users"."last_activity_on" AS t1_r55,
"users"."notified_of_own_activity" AS t1_r56,
"users"."preferred_language" AS t1_r57,
"users"."theme_id" AS t1_r58,
"users"."accepted_term_id" AS t1_r59,
"users"."feed_token" AS t1_r60,
"users"."private_profile" AS t1_r61,
"users"."roadmap_layout" AS t1_r62,
"users"."include_private_contributions" AS t1_r63,
"users"."commit_email" AS t1_r64,
"users"."group_view" AS t1_r65,
"users"."managing_group_id" AS t1_r66,
"users"."first_name" AS t1_r67,
"users"."last_name" AS t1_r68,
"users"."static_object_token" AS t1_r69,
"users"."role" AS t1_r70,
"users"."user_type" AS t1_r71,
"users"."static_object_token_encrypted" AS t1_r72,
"users"."otp_secret_expires_at" AS t1_r73,
"users"."onboarding_in_progress" AS t1_r74,
"users"."color_mode_id" AS t1_r75,
"users"."last_access_from_pipl_country_at" AS t1_r76
FROM "personal_access_tokens"
LEFT OUTER JOIN "users" ON "users"."id" = "personal_access_tokens"."user_id"
WHERE 1=1
AND (revoked = FALSE
AND seven_days_notification_sent_at IS NULL
AND expire_notification_delivered = FALSE
AND expires_at BETWEEN '2024-10-11' AND '2024-10-18')
AND "personal_access_tokens"."impersonation" = FALSE
AND "users"."user_type" = 6
ORDER BY "personal_access_tokens"."expires_at" ASC LIMIT 100
Update bot notification delivered columns
UPDATE "personal_access_tokens"
SET "seven_days_notification_sent_at" = '2024-10-11 08:25:44.774862',
"expire_notification_delivered" = TRUE
WHERE "personal_access_tokens"."id" IN
(SELECT "personal_access_tokens"."id"
FROM "personal_access_tokens"
LEFT OUTER JOIN "users" ON "users"."id" = "personal_access_tokens"."user_id"
WHERE 1=1
AND (revoked = FALSE
AND seven_days_notification_sent_at IS NULL
AND expire_notification_delivered = FALSE
AND expires_at BETWEEN '2024-10-11' AND '2024-10-18')
AND "personal_access_tokens"."impersonation" = FALSE
AND "users"."user_type" = 6
AND 1=1
ORDER BY "personal_access_tokens"."expires_at" ASC LIMIT 100)
For reference, here are the related queries on master:
Get user PATs (current master)
SELECT "personal_access_tokens"."user_id",
"personal_access_tokens"."id" AS t0_r0,
"users"."id" AS t1_r0,
"users"."email" AS t1_r1,
"users"."encrypted_password" AS t1_r2,
"users"."reset_password_token" AS t1_r3,
"users"."reset_password_sent_at" AS t1_r4,
"users"."remember_created_at" AS t1_r5,
"users"."sign_in_count" AS t1_r6,
"users"."current_sign_in_at" AS t1_r7,
"users"."last_sign_in_at" AS t1_r8,
"users"."current_sign_in_ip" AS t1_r9,
"users"."last_sign_in_ip" AS t1_r10,
"users"."created_at" AS t1_r11,
"users"."updated_at" AS t1_r12,
"users"."name" AS t1_r13,
"users"."admin" AS t1_r14,
"users"."projects_limit" AS t1_r15,
"users"."failed_attempts" AS t1_r16,
"users"."locked_at" AS t1_r17,
"users"."username" AS t1_r18,
"users"."can_create_group" AS t1_r19,
"users"."can_create_team" AS t1_r20,
"users"."state" AS t1_r21,
"users"."color_scheme_id" AS t1_r22,
"users"."password_expires_at" AS t1_r23,
"users"."created_by_id" AS t1_r24,
"users"."last_credential_check_at" AS t1_r25,
"users"."avatar" AS t1_r26,
"users"."confirmation_token" AS t1_r27,
"users"."confirmed_at" AS t1_r28,
"users"."confirmation_sent_at" AS t1_r29,
"users"."unconfirmed_email" AS t1_r30,
"users"."hide_no_ssh_key" AS t1_r31,
"users"."admin_email_unsubscribed_at" AS t1_r32,
"users"."notification_email" AS t1_r33,
"users"."hide_no_password" AS t1_r34,
"users"."password_automatically_set" AS t1_r35,
"users"."encrypted_otp_secret" AS t1_r36,
"users"."encrypted_otp_secret_iv" AS t1_r37,
"users"."encrypted_otp_secret_salt" AS t1_r38,
"users"."otp_required_for_login" AS t1_r39,
"users"."otp_backup_codes" AS t1_r40,
"users"."public_email" AS t1_r41,
"users"."dashboard" AS t1_r42,
"users"."project_view" AS t1_r43,
"users"."consumed_timestep" AS t1_r44,
"users"."layout" AS t1_r45,
"users"."hide_project_limit" AS t1_r46,
"users"."note" AS t1_r47,
"users"."unlock_token" AS t1_r48,
"users"."otp_grace_period_started_at" AS t1_r49,
"users"."external" AS t1_r50,
"users"."incoming_email_token" AS t1_r51,
"users"."auditor" AS t1_r52,
"users"."require_two_factor_authentication_from_group" AS t1_r53,
"users"."two_factor_grace_period" AS t1_r54,
"users"."last_activity_on" AS t1_r55,
"users"."notified_of_own_activity" AS t1_r56,
"users"."preferred_language" AS t1_r57,
"users"."theme_id" AS t1_r58,
"users"."accepted_term_id" AS t1_r59,
"users"."feed_token" AS t1_r60,
"users"."private_profile" AS t1_r61,
"users"."roadmap_layout" AS t1_r62,
"users"."include_private_contributions" AS t1_r63,
"users"."commit_email" AS t1_r64,
"users"."group_view" AS t1_r65,
"users"."managing_group_id" AS t1_r66,
"users"."first_name" AS t1_r67,
"users"."last_name" AS t1_r68,
"users"."static_object_token" AS t1_r69,
"users"."role" AS t1_r70,
"users"."user_type" AS t1_r71,
"users"."static_object_token_encrypted" AS t1_r72,
"users"."otp_secret_expires_at" AS t1_r73,
"users"."onboarding_in_progress" AS t1_r74,
"users"."color_mode_id" AS t1_r75,
"users"."last_access_from_pipl_country_at" AS t1_r76
FROM "personal_access_tokens"
LEFT OUTER JOIN "users" ON "users"."id" = "personal_access_tokens"."user_id"
WHERE ((revoked = FALSE
AND expire_notification_delivered = FALSE
AND expires_at >= CURRENT_DATE
AND expires_at <= '2024-09-27')
AND impersonation = FALSE)
AND "users"."user_type" = 0 LIMIT 100
Update user tokens delivery column (master)
UPDATE "personal_access_tokens"
SET "expire_notification_delivered" = TRUE
WHERE "personal_access_tokens"."user_id" = 1
AND ((revoked = FALSE
AND expire_notification_delivered = FALSE
AND expires_at >= CURRENT_DATE
AND expires_at <= '2024-09-27')
AND impersonation = FALSE)
Select project and resource access tokens (master)
SELECT "personal_access_tokens"."id",
"personal_access_tokens"."user_id",
"personal_access_tokens"."id" AS t0_r0,
"users"."id" AS t1_r0,
"users"."email" AS t1_r1,
"users"."encrypted_password" AS t1_r2,
"users"."reset_password_token" AS t1_r3,
"users"."reset_password_sent_at" AS t1_r4,
"users"."remember_created_at" AS t1_r5,
"users"."sign_in_count" AS t1_r6,
"users"."current_sign_in_at" AS t1_r7,
"users"."last_sign_in_at" AS t1_r8,
"users"."current_sign_in_ip" AS t1_r9,
"users"."last_sign_in_ip" AS t1_r10,
"users"."created_at" AS t1_r11,
"users"."updated_at" AS t1_r12,
"users"."name" AS t1_r13,
"users"."admin" AS t1_r14,
"users"."projects_limit" AS t1_r15,
"users"."failed_attempts" AS t1_r16,
"users"."locked_at" AS t1_r17,
"users"."username" AS t1_r18,
"users"."can_create_group" AS t1_r19,
"users"."can_create_team" AS t1_r20,
"users"."state" AS t1_r21,
"users"."color_scheme_id" AS t1_r22,
"users"."password_expires_at" AS t1_r23,
"users"."created_by_id" AS t1_r24,
"users"."last_credential_check_at" AS t1_r25,
"users"."avatar" AS t1_r26,
"users"."confirmation_token" AS t1_r27,
"users"."confirmed_at" AS t1_r28,
"users"."confirmation_sent_at" AS t1_r29,
"users"."unconfirmed_email" AS t1_r30,
"users"."hide_no_ssh_key" AS t1_r31,
"users"."admin_email_unsubscribed_at" AS t1_r32,
"users"."notification_email" AS t1_r33,
"users"."hide_no_password" AS t1_r34,
"users"."password_automatically_set" AS t1_r35,
"users"."encrypted_otp_secret" AS t1_r36,
"users"."encrypted_otp_secret_iv" AS t1_r37,
"users"."encrypted_otp_secret_salt" AS t1_r38,
"users"."otp_required_for_login" AS t1_r39,
"users"."otp_backup_codes" AS t1_r40,
"users"."public_email" AS t1_r41,
"users"."dashboard" AS t1_r42,
"users"."project_view" AS t1_r43,
"users"."consumed_timestep" AS t1_r44,
"users"."layout" AS t1_r45,
"users"."hide_project_limit" AS t1_r46,
"users"."note" AS t1_r47,
"users"."unlock_token" AS t1_r48,
"users"."otp_grace_period_started_at" AS t1_r49,
"users"."external" AS t1_r50,
"users"."incoming_email_token" AS t1_r51,
"users"."auditor" AS t1_r52,
"users"."require_two_factor_authentication_from_group" AS t1_r53,
"users"."two_factor_grace_period" AS t1_r54,
"users"."last_activity_on" AS t1_r55,
"users"."notified_of_own_activity" AS t1_r56,
"users"."preferred_language" AS t1_r57,
"users"."theme_id" AS t1_r58,
"users"."accepted_term_id" AS t1_r59,
"users"."feed_token" AS t1_r60,
"users"."private_profile" AS t1_r61,
"users"."roadmap_layout" AS t1_r62,
"users"."include_private_contributions" AS t1_r63,
"users"."commit_email" AS t1_r64,
"users"."group_view" AS t1_r65,
"users"."managing_group_id" AS t1_r66,
"users"."first_name" AS t1_r67,
"users"."last_name" AS t1_r68,
"users"."static_object_token" AS t1_r69,
"users"."role" AS t1_r70,
"users"."user_type" AS t1_r71,
"users"."static_object_token_encrypted" AS t1_r72,
"users"."otp_secret_expires_at" AS t1_r73,
"users"."onboarding_in_progress" AS t1_r74,
"users"."color_mode_id" AS t1_r75,
"users"."last_access_from_pipl_country_at" AS t1_r76
FROM "personal_access_tokens"
LEFT OUTER JOIN "users" ON "users"."id" = "personal_access_tokens"."user_id"
WHERE 1=1
AND ((revoked = FALSE
AND expire_notification_delivered = FALSE
AND expires_at >= CURRENT_DATE
AND expires_at <= '2024-09-27')
AND impersonation = FALSE)
AND "users"."user_type" = 6 LIMIT 100
Screenshots or screen recordings
Screenshots are required for UI changes, and strongly recommended for all other merge requests.
| Before | After |
|---|---|
How to set up and validate locally
- Enable the feature:
Feature.enable(:expiring_pats_30d_60d_notifications) - Go to your account Personal Access Tokens:
https://gdk.test:3443/-/user_settings/personal_access_tokens - Create 3 access tokens:
- expiring in 5 days
- expiring in 20 days
- expiring in 55 days
- Go to access tokens for a project you own (with Ultimate plan if emulating SaaS):
https://gdk.test:3443/flightjs/Flight/-/settings/access_tokens - Create 3 access tokens:
- expiring in 5 days
- expiring in 20 days
- expiring in 55 days
- Go to access tokens for a group you own (with Ultimate plan if emulating SaaS):
https://gdk.test:3443/flightjs/-/settings/access_tokens- expiring in 5 days
- expiring in 20 days
- expiring in 55 days
- Run the worker from the Rails console:
PersonalAccessTokens::ExpiringWorker.new.performor enqueue fromhttps://gdk.test:3443/admin/sidekiq/cron - Go to letter_opener and check for notifications about the expiring tokens:
https://gdk.test:3443/rails/letter_opener - If you don't see the emails, you can validate that the mailers are enqueued in Sidekiq at
https://gdk.test:3443/admin/sidekiq/queues/mailers
Related to #464040 (closed)