appstore: add unlink account route

This commit is contained in:
Girish Ramakrishnan
2025-09-24 21:25:31 +02:00
parent a38ef2b6f5
commit 98d4d99c1b
11 changed files with 37 additions and 54 deletions
+6 -6
View File
@@ -907,12 +907,12 @@
"subscriptionChangeAction": "Manage Subscription",
"subscriptionReactivateAction": "Reactivate Subscription",
"emailNotVerified": "Email not yet verified",
"resetDialog": {
"title": "Really reset Cloudron ID",
"description": "This will disconnect the Cloudron from the current cloudron.io account."
},
"resetAction": "Reset Registration",
"account": "Account"
"account": "Account",
"unlinkAction": "Unlink Account",
"unlinkDialog": {
"title": "Unlink Cloudron.io Account",
"description": "This will unlink this Cloudron from the current Cloudron.io Account. It can then be <a href=\"https://docs.cloudron.io/appstore/#account-change\" target=\"_blank\">linked</a> with another account."
}
},
"timezone": {
"title": "System Time Zone",
+1 -6
View File
@@ -1383,12 +1383,7 @@
"subscriptionReactivateAction": "Abonnement heractiveren",
"title": "Cloudron.io Account",
"description": "Een Cloudron.io account wordt gebruikt voor toegang tot de App Store en om je abonnement te beheren.",
"emailNotVerified": "E-mail is niet geverifieerd",
"resetDialog": {
"title": "Weet je zeker dat je je Cloudron ID wilt resetten",
"description": "Hierdoor wordt deze Cloudron losgekoppeld van het huidige Cloudron.io account."
},
"resetAction": "Reset Registratie"
"emailNotVerified": "E-mail is niet geverifieerd"
},
"timezone": {
"title": "Systeem Tijdzone",
+1 -6
View File
@@ -887,12 +887,7 @@
"cloudronId": "Id. de Cloudron",
"subscriptionChangeAction": "Gerir Subscrição",
"subscriptionReactivateAction": "Reativar Subscrição",
"emailNotVerified": "E-mail ainda não foi verificado",
"resetDialog": {
"title": "Deseja redefinir a Id. do Cloudron",
"description": "Isto desligará o Cloudron da conta Cloudron.io atual."
},
"resetAction": "Reiniciar Registo"
"emailNotVerified": "E-mail ainda não foi verificado"
},
"updates": {
"checkForUpdatesAction": "Procurar por Atualizações",
+1 -6
View File
@@ -1282,12 +1282,7 @@
"subscriptionEndsAt": "Отменена и завершена",
"subscriptionChangeAction": "Управление подпиской",
"subscriptionReactivateAction": "Реактивировать подписку",
"emailNotVerified": "Электронная почта не подтверждена",
"resetDialog": {
"title": "Полностью сбросить Cloudron ID",
"description": "Это действие отвяжет настоящий аккаунт cloudron.io от данного Cloudron."
},
"resetAction": "Сбросить регистрацию"
"emailNotVerified": "Электронная почта не подтверждена"
},
"timezone": {
"title": "Системный часовой пояс",
+1 -6
View File
@@ -1222,12 +1222,7 @@
"setupAction": "Cài đặt tài khoản",
"description": "Tài khoản Cloudron.io được dùng để truy cập Cửa hàng App và quản lý gói đăng ký.",
"title": "Tài khoản Cloudron.io",
"emailNotVerified": "Địa chỉ email chưa được xác minh",
"resetDialog": {
"title": "Chắc chắn cài đặt lại Cloudron ID",
"description": "Việc này sẽ ngắt kết nối Cloudron khỏi tài khoản cloudron.io hiện tại."
},
"resetAction": "Cài đặt lại Đăng ký"
"emailNotVerified": "Địa chỉ email chưa được xác minh"
},
"title": "Hệ thống"
},
+2 -2
View File
@@ -42,10 +42,10 @@ function create() {
if (error || result.status !== 200) return [error || result];
return [null, result.body];
},
async resetCloudronId() {
async unlinkAccount() {
let result;
try {
result = await fetcher.post(`${API_ORIGIN}/api/v1/appstore/reset_cloudron_id`, {}, { access_token: accessToken });
result = await fetcher.post(`${API_ORIGIN}/api/v1/appstore/unlink_account`, {}, { access_token: accessToken });
} catch (e) {
return [e];
}
+8 -8
View File
@@ -49,10 +49,10 @@ async function refresh() {
}
const inputDialog = useTemplateRef('inputDialog');
async function onAskResetCloudron() {
async function onAskUnlinkAccount() {
const yes = await inputDialog.value.confirm({
title: t('settings.appstoreAccount.resetDialog.title'),
message: t('settings.appstoreAccount.resetDialog.description'),
title: t('settings.appstoreAccount.unlinkDialog.title'),
message: t('settings.appstoreAccount.unlinkDialog.description'),
confirmStyle: 'danger',
confirmLabel: t('main.dialog.yes'),
rejectLabel: t('main.dialog.cancel'),
@@ -61,13 +61,13 @@ async function onAskResetCloudron() {
if (!yes) return;
await onResetCloudron();
await onUnlinkAccount();
}
async function onResetCloudron() {
async function onUnlinkAccount() {
busy.value = true;
const [error] = await appstoreModel.resetCloudronId();
const [error] = await appstoreModel.unlinkAccount();
if (error) return console.error(error);
await refresh();
@@ -129,7 +129,7 @@ onUnmounted(() => {
<span v-else>{{ $t('settings.appstoreAccount.subscriptionChangeAction') }}</span>
</Button>
<Button secondary @click="onAskResetCloudron" v-if="email">{{ $t('settings.appstoreAccount.resetAction') }}</Button>
<Button secondary @click="onAskUnlinkAccount" v-if="email">{{ $t('settings.appstoreAccount.unlinkAction') }}</Button>
</div>
</div>
@@ -147,7 +147,7 @@ onUnmounted(() => {
Unknown Cloudron ID or invalid cloudron.io token.
</div>
<div style="display: flex; align-items: center;">
<Button @click="onResetCloudron">{{ $t('settings.appstoreAccount.resetAction') }}</Button>
<Button @click="onUnlinkAccount">{{ $t('settings.appstoreAccount.unlinkAction') }}</Button>
</div>
</SettingsItem>
</div>
+12 -3
View File
@@ -17,6 +17,7 @@ exports = module.exports = {
registerCloudron3,
updateCloudron,
unlinkAccount,
getSubscription,
isFreePlan,
@@ -32,6 +33,7 @@ exports = module.exports = {
const assert = require('node:assert'),
BoxError = require('./boxerror.js'),
constants = require('./constants.js'),
dashboard = require('./dashboard.js'),
debug = require('debug')('box:appstore'),
manifestFormat = require('@cloudron/manifest-format'),
paths = require('./paths.js'),
@@ -223,9 +225,9 @@ async function getAppUpdate(app, options) {
return updateInfo;
}
async function registerCloudron3(domain, version) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof version, 'string');
async function registerCloudron3() {
const { domain } = await dashboard.getLocation();
const version = constants.VERSION;
const token = await settings.get(settings.APPSTORE_API_TOKEN_KEY);
if (token) { // when installed using setupToken, this updates the domain record when called during provisioning
@@ -250,6 +252,13 @@ async function registerCloudron3(domain, version) {
debug(`registerCloudron3: Cloudron registered with id ${response.body.cloudronId}`);
}
async function unlinkAccount() {
debug('unlinkAccount: Unlinking existing account.');
await unregister();
return await registerCloudron3();
}
async function updateCloudron(data) {
assert.strictEqual(typeof data, 'object');
+1 -2
View File
@@ -151,8 +151,7 @@ async function activate(username, password, email, displayName, ip, auditSource)
debug(`activate: user: ${username} email:${email}`);
const dashboardLocation = await dashboard.getLocation();
await appstore.registerCloudron3(dashboardLocation.domain, constants.VERSION);
await appstore.registerCloudron3();
const [error, ownerId] = await safe(users.createOwner(email, username, password, displayName, auditSource));
if (error && error.reason === BoxError.ALREADY_EXISTS) throw new BoxError(BoxError.CONFLICT, 'Already activated');
+3 -8
View File
@@ -5,15 +5,13 @@ exports = module.exports = {
getApp,
getAppVersion,
resetCloudronId,
unlinkAccount,
getSubscription
};
const appstore = require('../appstore.js'),
assert = require('node:assert'),
BoxError = require('../boxerror.js'),
constants = require('../constants.js'),
dashboard = require('../dashboard.js'),
HttpSuccess = require('@cloudron/connect-lastmile').HttpSuccess,
safe = require('safetydance'),
users = require('../users.js'),
@@ -45,13 +43,10 @@ async function getAppVersion(req, res, next) {
next(new HttpSuccess(200, manifest));
}
async function resetCloudronId(req, res, next) {
async function unlinkAccount(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
const [getLocationError, dashboardLocation] = await safe(dashboard.getLocation()); // authenticated route implies already activated
if (getLocationError) return next(BoxError.toHttpError(getLocationError));
const [registerError] = await safe(appstore.registerCloudron3(dashboardLocation.domain, constants.VERSION));
const [registerError] = await safe(appstore.unlinkAccount());
if (registerError) return next(BoxError.toHttpError(registerError));
next(new HttpSuccess(202, {}));
+1 -1
View File
@@ -256,7 +256,7 @@ async function initializeExpressSync() {
router.post('/api/v1/directory_server/config', json, token, authorizeAdmin, routes.directoryServer.setConfig);
// appstore and subscription routes
router.post('/api/v1/appstore/reset_cloudron_id', json, token, authorizeOwner, routes.appstore.resetCloudronId);
router.post('/api/v1/appstore/unlink_account', json, token, authorizeOwner, routes.appstore.unlinkAccount);
router.get ('/api/v1/appstore/subscription', token, authorizeUser, routes.appstore.getSubscription); // for all users
router.get ('/api/v1/appstore/apps', token, authorizeAdmin, routes.appstore.getApps);
router.get ('/api/v1/appstore/apps/:appstoreId', token, authorizeAdmin, routes.appstore.getApp);