diff --git a/services/web/app/src/Features/User/UserPagesController.js b/services/web/app/src/Features/User/UserPagesController.js
index 4b33e60458..059b2c9a0c 100644
--- a/services/web/app/src/Features/User/UserPagesController.js
+++ b/services/web/app/src/Features/User/UserPagesController.js
@@ -68,6 +68,18 @@ async function settingsPage(req, res) {
const showPersonalAccessToken =
!Features.hasFeature('saas') || req.query?.personal_access_token === 'true'
+ let personalAccessTokens
+ if (showPersonalAccessToken) {
+ try {
+ // require this here because module may not be included in some versions
+ const PersonalAccessTokenManager = require('../../../../modules/oauth2-server/app/src/OAuthPersonalAccessTokenManager')
+ personalAccessTokens = await PersonalAccessTokenManager.listTokens(
+ user._id
+ )
+ } catch (error) {
+ logger.error(OError.tag(error))
+ }
+ }
res.render('user/settings', {
title: 'account_settings',
@@ -112,6 +124,7 @@ async function settingsPage(req, res) {
thirdPartyIds: UserPagesController._restructureThirdPartyIds(user),
projectSyncSuccessMessage,
showPersonalAccessToken,
+ personalAccessTokens,
})
}
diff --git a/services/web/app/views/user/settings.pug b/services/web/app/views/user/settings.pug
index 73fcc77bab..caf2b69fc6 100644
--- a/services/web/app/views/user/settings.pug
+++ b/services/web/app/views/user/settings.pug
@@ -23,6 +23,7 @@ block append meta
meta(name="ol-github" data-type="json" content=github)
meta(name="ol-projectSyncSuccessMessage", content=projectSyncSuccessMessage)
meta(name="ol-showPersonalAccessToken", data-type="boolean" content=showPersonalAccessToken)
+ meta(name="ol-personalAccessTokens", data-type="json" content=personalAccessTokens)
block content
main.content.content-alt#settings-page-root
diff --git a/services/web/config/settings.defaults.js b/services/web/config/settings.defaults.js
index c95c48cab3..46c8e7b151 100644
--- a/services/web/config/settings.defaults.js
+++ b/services/web/config/settings.defaults.js
@@ -805,6 +805,7 @@ module.exports = {
importProjectFromGithubMenu: [],
editorLeftMenuSync: [],
editorLeftMenuManageTemplate: [],
+ oauth2Server: [],
},
moduleImportSequence: [
diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json
index 1e0157abf0..0218e4adf4 100644
--- a/services/web/frontend/extracted-translations.json
+++ b/services/web/frontend/extracted-translations.json
@@ -24,6 +24,7 @@
"add_affiliation": "",
"add_another_address_line": "",
"add_another_email": "",
+ "add_another_token": "",
"add_comma_separated_emails_help": "",
"add_company_details": "",
"add_email_to_claim_features": "",
@@ -155,6 +156,7 @@
"contact_support_to_change_group_subscription": "",
"contact_us": "",
"continue_github_merge": "",
+ "copied": "",
"copy": "",
"copy_project": "",
"copying": "",
@@ -168,6 +170,7 @@
"create_new_subscription": "",
"create_new_tag": "",
"create_project_in_github": "",
+ "created": "",
"created_at": "",
"creating": "",
"current_password": "",
@@ -184,9 +187,12 @@
"delete_acct_no_existing_pw": "",
"delete_and_leave": "",
"delete_and_leave_projects": "",
+ "delete_authentication_token": "",
+ "delete_authentication_token_info": "",
"delete_figure": "",
"delete_projects": "",
"delete_tag": "",
+ "delete_token": "",
"delete_your_account": "",
"deleted_at": "",
"deleting": "",
@@ -259,6 +265,8 @@
"example_project": "",
"existing_plan_active_until_term_end": "",
"expand": "",
+ "expired": "",
+ "expires": "",
"export_csv": "",
"export_project_to_github": "",
"fast": "",
@@ -329,6 +337,7 @@
"galileo_suggestion_feedback_button": "",
"galileo_suggestions_loading_error": "",
"galileo_toggle_description": "",
+ "generate_token": "",
"generic_if_problem_continues_contact_us": "",
"generic_linked_file_compile_error": "",
"generic_something_went_wrong": "",
@@ -337,7 +346,12 @@
"get_most_subscription_by_checking_features": "",
"get_most_subscription_by_checking_premium_features": "",
"git": "",
+ "git_authentication_token": "",
+ "git_authentication_token_create_modal_info_1": "",
+ "git_authentication_token_create_modal_info_2": "",
"git_bridge_modal_description": "",
+ "git_integration": "",
+ "git_integration_info": "",
"github_commit_message_placeholder": "",
"github_credentials_expired": "",
"github_file_name_error": "",
@@ -477,6 +491,7 @@
"last_name": "",
"last_resort_trouble_shooting_guide": "",
"last_updated_date_by_x": "",
+ "last_used": "",
"latex_help_guide": "",
"latex_places_figures_according_to_a_special_algorithm": "",
"layout": "",
@@ -933,6 +948,8 @@
"to_change_access_permissions": "",
"to_modify_your_subscription_go_to": "",
"toggle_compile_options_menu": "",
+ "token": "",
+ "token_limit_reached": "",
"token_read_only": "",
"token_read_write": "",
"too_many_attempts": "",
@@ -1071,6 +1088,13 @@
"you_have_added_x_of_group_size_y": "",
"your_affiliation_is_confirmed": "",
"your_browser_does_not_support_this_feature": "",
+ "your_git_access_info": "",
+ "your_git_access_info_bullet_1": "",
+ "your_git_access_info_bullet_2": "",
+ "your_git_access_info_bullet_3": "",
+ "your_git_access_info_bullet_4": "",
+ "your_git_access_info_bullet_5": "",
+ "your_git_access_tokens": "",
"your_message_to_collaborators": "",
"your_new_plan": "",
"your_plan": "",
diff --git a/services/web/frontend/js/features/settings/components/linking-section.tsx b/services/web/frontend/js/features/settings/components/linking-section.tsx
index 9e01ea4c29..0d769d0649 100644
--- a/services/web/frontend/js/features/settings/components/linking-section.tsx
+++ b/services/web/frontend/js/features/settings/components/linking-section.tsx
@@ -1,4 +1,4 @@
-import { useState } from 'react'
+import { useState, ElementType } from 'react'
import { Alert } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
@@ -24,7 +24,20 @@ function LinkingSection() {
importOverleafModules('referenceLinkingWidgets')
)
- const hasIntegrationLinkingSection = integrationLinkingWidgets.length
+ const oauth2ServerComponents = importOverleafModules('oauth2Server') as {
+ import: { default: ElementType }
+ path: string
+ }[]
+
+ const showPersonalAccessToken = getMeta(
+ 'ol-showPersonalAccessToken'
+ ) as boolean
+
+ const allIntegrationLinkingWidgets = showPersonalAccessToken
+ ? integrationLinkingWidgets.concat(oauth2ServerComponents)
+ : integrationLinkingWidgets
+
+ const hasIntegrationLinkingSection = allIntegrationLinkingWidgets.length
const hasReferencesLinkingSection = referenceLinkingWidgets.length
const hasSSOLinkingSection = Object.keys(subscriptions).length > 0
@@ -49,12 +62,14 @@ function LinkingSection() {
git clone your project using the link displayed below.",
+ "git_integration": "Git Integration",
+ "git_integration_info": "With Git integration, you can clone your Overleaf projects with Git. For full instructions on how to do this, read <0>our help page0>.",
"git_integration_lowercase": "Git integration",
"git_integration_lowercase_info": "You can clone your Overleaf project to a local repository, treating your Overleaf project as a remote repository that changes can be pushed to and pulled from.",
"github_commit_message_placeholder": "Commit message for changes made in __appName__...",
@@ -814,6 +827,7 @@
"last_resort_trouble_shooting_guide": "If that doesn’t help, follow our <0>troubleshooting guide0>.",
"last_updated": "Last Updated",
"last_updated_date_by_x": "__lastUpdatedDate__ by __person__",
+ "last_used": "last used",
"latex_editor_info": "Everything you need in a modern LaTeX editor --- spell check, intelligent autocomplete, syntax highlighting, dozens of color themes, vim and emacs bindings, help with LaTeX warnings and error messages, and much more.",
"latex_guides": "LaTeX guides",
"latex_help_guide": "LaTeX help guide",
@@ -1571,7 +1585,9 @@
"to_many_login_requests_2_mins": "This account has had too many login requests. Please wait 2 minutes before trying to log in again",
"to_modify_your_subscription_go_to": "To modify your subscription go to",
"toggle_compile_options_menu": "Toggle compile options menu",
+ "token": "token",
"token_access_failure": "Cannot grant access; contact the project owner for help",
+ "token_limit_reached": "You’ve reached the 10 token limit. To generate a new authentication token, please delete an existing one.",
"token_read_only": "token read-only",
"token_read_write": "token read-write",
"too_many_attempts": "Too many attempts. Please wait for a while and try again.",
@@ -1778,6 +1794,13 @@
"you_will_be_able_to_contact_us_any_time_to_share_your_feedback": "<0>You will be able to contact us0> any time to share your feedback",
"your_affiliation_is_confirmed": "Your <0>__institutionName__0> affiliation is confirmed.",
"your_browser_does_not_support_this_feature": "Sorry, your browser doesn’t support this feature. Please update your browser to its latest version.",
+ "your_git_access_info": "Your Git authentication tokens should be entered whenever you’re prompted for a password.",
+ "your_git_access_info_bullet_1": "You can have up to 10 tokens.",
+ "your_git_access_info_bullet_2": "If you reach the maximum limit, you’ll need to delete a token before you can generate a new one.",
+ "your_git_access_info_bullet_3": "You can generate a token using the <0>Generate token0> button.",
+ "your_git_access_info_bullet_4": "You won’t be able to view the full token after the first time you generate it. Please copy it and keep it safe",
+ "your_git_access_info_bullet_5": "Previously generated tokens will be shown here.",
+ "your_git_access_tokens": "Your Git authentication tokens",
"your_message_to_collaborators": "Send a message to your collaborators",
"your_new_plan": "Your new plan",
"your_password_has_been_successfully_changed": "Your password has been successfully changed",
diff --git a/services/web/test/unit/src/User/UserPagesControllerTests.js b/services/web/test/unit/src/User/UserPagesControllerTests.js
index 8504ffe67b..4d7bf67506 100644
--- a/services/web/test/unit/src/User/UserPagesControllerTests.js
+++ b/services/web/test/unit/src/User/UserPagesControllerTests.js
@@ -70,6 +70,9 @@ describe('UserPagesController', function () {
this.Features = {
hasFeature: sinon.stub().returns(false),
}
+ this.PersonalAccessTokenManager = {
+ listTokens: sinon.stub().returns([]),
+ }
this.UserPagesController = SandboxedModule.require(modulePath, {
requires: {
'@overleaf/settings': this.settings,
@@ -80,6 +83,8 @@ describe('UserPagesController', function () {
'../Authentication/AuthenticationController':
this.AuthenticationController,
'../../infrastructure/Features': this.Features,
+ '../../../../modules/oauth2-server/app/src/OAuthPersonalAccessTokenManager':
+ this.PersonalAccessTokenManager,
'../Authentication/SessionManager': this.SessionManager,
request: (this.request = sinon.stub()),
},
diff --git a/services/web/types/window.ts b/services/web/types/window.ts
index 7706f37c0c..f74b011e78 100644
--- a/services/web/types/window.ts
+++ b/services/web/types/window.ts
@@ -32,5 +32,8 @@ declare global {
_reportAcePerf: () => void
MathJax: Record