Spiegel von
https://github.com/paviliondev/discourse-custom-wizard.git
synchronisiert 2024-11-25 02:30:28 +01:00
Update authentication and subscription handling
Dieser Commit ist enthalten in:
Ursprung
247f7ca466
Commit
a27c222dc6
24 geänderte Dateien mit 687 neuen und 8 gelöschten Zeilen
|
@ -6,6 +6,7 @@ import I18n from "I18n";
|
||||||
const icons = {
|
const icons = {
|
||||||
error: "times-circle",
|
error: "times-circle",
|
||||||
success: "check-circle",
|
success: "check-circle",
|
||||||
|
warn: "exclamation-circle",
|
||||||
info: "info-circle",
|
info: "info-circle",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
import CustomWizardPro from "../models/custom-wizard-pro";
|
||||||
|
import { notEmpty } from "@ember/object/computed";
|
||||||
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
classNameBindings: [':custom-wizard-pro-subscription', 'subscription.active:active:inactive'],
|
||||||
|
subscribed: notEmpty('subscription'),
|
||||||
|
|
||||||
|
@discourseComputed('subscription.type')
|
||||||
|
title(type) {
|
||||||
|
return type ?
|
||||||
|
I18n.t(`admin.wizard.pro.subscription.title.${type}`) :
|
||||||
|
I18n.t("admin.wizard.pro.not_subscribed");
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed('subscription.active')
|
||||||
|
stateClass(active) {
|
||||||
|
return active ? 'active' : 'inactive';
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed('stateClass')
|
||||||
|
stateLabel(stateClass) {
|
||||||
|
return I18n.t(`admin.wizard.pro.subscription.status.${stateClass}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
update() {
|
||||||
|
this.set('updating', true);
|
||||||
|
CustomWizardPro.update_subscription().then(result => {
|
||||||
|
if (result.success) {
|
||||||
|
this.setProperties({
|
||||||
|
updateIcon: 'check',
|
||||||
|
subscription: result.subscription
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.set('updateIcon', 'times');
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
this.set('updating', false);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.set('updateIcon', null);
|
||||||
|
}, 7000);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
56
assets/javascripts/discourse/controllers/admin-wizards-pro.js.es6
Normale Datei
56
assets/javascripts/discourse/controllers/admin-wizards-pro.js.es6
Normale Datei
|
@ -0,0 +1,56 @@
|
||||||
|
import Controller from "@ember/controller";
|
||||||
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
import CustomWizardPro from "../models/custom-wizard-pro";
|
||||||
|
import { alias } from "@ember/object/computed";
|
||||||
|
|
||||||
|
export default Controller.extend({
|
||||||
|
messageUrl: "https://thepavilion.io/t/3652",
|
||||||
|
messageType: 'info',
|
||||||
|
messageKey: null,
|
||||||
|
showSubscription: alias('model.authentication.active'),
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
const authentication = this.get('model.authentication');
|
||||||
|
const subscription = this.get('model.subscription');
|
||||||
|
const subscribed = subscription && subscription.active;
|
||||||
|
const authenticated = authentication && authentication.active;
|
||||||
|
|
||||||
|
if (!subscribed) {
|
||||||
|
this.set('messageKey', authenticated ? 'not_subscribed' : 'authorize');
|
||||||
|
} else {
|
||||||
|
this.set('messageKey', !authenticated ?
|
||||||
|
'subscription_expiring' :
|
||||||
|
subscribed ? 'subscription_active' : 'subscription_inactive'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed('model.server')
|
||||||
|
messageOpts(server) {
|
||||||
|
return { server };
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
unauthorize() {
|
||||||
|
this.set('unauthorizing', true);
|
||||||
|
|
||||||
|
CustomWizardPro.unauthorize().then(result => {
|
||||||
|
if (result.success) {
|
||||||
|
this.setProperties({
|
||||||
|
messageKey: 'unauthorized',
|
||||||
|
messageType: 'warn',
|
||||||
|
"model.authentication": null,
|
||||||
|
"model.subscription": null
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setProperties({
|
||||||
|
messageKey: 'unauthorize_failed',
|
||||||
|
messageType: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
this.set('unauthorizing', false);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -43,12 +43,20 @@ export default {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.route("adminWizardsLogs", { path: "/logs", resetNamespace: true });
|
this.route("adminWizardsLogs", {
|
||||||
|
path: "/logs",
|
||||||
|
resetNamespace: true
|
||||||
|
});
|
||||||
|
|
||||||
this.route("adminWizardsManager", {
|
this.route("adminWizardsManager", {
|
||||||
path: "/manager",
|
path: "/manager",
|
||||||
resetNamespace: true,
|
resetNamespace: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.route("adminWizardsPro", {
|
||||||
|
path: "/pro",
|
||||||
|
resetNamespace: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
34
assets/javascripts/discourse/models/custom-wizard-pro.js.es6
Normale Datei
34
assets/javascripts/discourse/models/custom-wizard-pro.js.es6
Normale Datei
|
@ -0,0 +1,34 @@
|
||||||
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
import EmberObject from "@ember/object";
|
||||||
|
import DiscourseURL from "discourse/lib/url";
|
||||||
|
|
||||||
|
const CustomWizardPro = EmberObject.extend();
|
||||||
|
|
||||||
|
const basePath = "/admin/wizards/pro";
|
||||||
|
|
||||||
|
CustomWizardPro.reopenClass({
|
||||||
|
status() {
|
||||||
|
return ajax(basePath, {
|
||||||
|
type: "GET",
|
||||||
|
}).catch(popupAjaxError);
|
||||||
|
},
|
||||||
|
|
||||||
|
authorize() {
|
||||||
|
window.location.href = `${basePath}/authorize`;
|
||||||
|
},
|
||||||
|
|
||||||
|
unauthorize() {
|
||||||
|
return ajax(`${basePath}/authorize`, {
|
||||||
|
type: "DELETE",
|
||||||
|
}).catch(popupAjaxError);
|
||||||
|
},
|
||||||
|
|
||||||
|
update_subscription() {
|
||||||
|
return ajax(`${basePath}/subscription`, {
|
||||||
|
type: "POST",
|
||||||
|
}).catch(popupAjaxError);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default CustomWizardPro;
|
|
@ -221,7 +221,7 @@ CustomWizard.reopenClass({
|
||||||
const wizard = this._super.apply(this);
|
const wizard = this._super.apply(this);
|
||||||
wizard.setProperties(buildProperties(wizardJson));
|
wizard.setProperties(buildProperties(wizardJson));
|
||||||
return wizard;
|
return wizard;
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default CustomWizard;
|
export default CustomWizard;
|
||||||
|
|
20
assets/javascripts/discourse/routes/admin-wizards-pro.js.es6
Normale Datei
20
assets/javascripts/discourse/routes/admin-wizards-pro.js.es6
Normale Datei
|
@ -0,0 +1,20 @@
|
||||||
|
import CustomWizardPro from "../models/custom-wizard-pro";
|
||||||
|
import DiscourseRoute from "discourse/routes/discourse";
|
||||||
|
|
||||||
|
export default DiscourseRoute.extend({
|
||||||
|
model() {
|
||||||
|
return CustomWizardPro.status();
|
||||||
|
},
|
||||||
|
|
||||||
|
setupController(controller, model) {
|
||||||
|
console.log(model)
|
||||||
|
controller.set('model', model);
|
||||||
|
controller.setup();
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
authorize() {
|
||||||
|
CustomWizardPro.authorize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
31
assets/javascripts/discourse/templates/admin-wizards-pro.hbs
Normale Datei
31
assets/javascripts/discourse/templates/admin-wizards-pro.hbs
Normale Datei
|
@ -0,0 +1,31 @@
|
||||||
|
<div class="admin-wizard-controls">
|
||||||
|
<h3>{{i18n "admin.wizard.pro.title"}}</h3>
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
{{#if model.authentication.active}}
|
||||||
|
{{conditional-loading-spinner size="small" condition=unauthorizing}}
|
||||||
|
<a {{action "unauthorize"}} title={{i18n "admin.wizard.pro.unauthorize"}}>
|
||||||
|
{{i18n "admin.wizard.pro.unauthorize"}}
|
||||||
|
</a>
|
||||||
|
<label>{{i18n "admin.wizard.pro.authorized"}}</label>
|
||||||
|
{{else}}
|
||||||
|
{{d-button
|
||||||
|
icon="id-card"
|
||||||
|
label="admin.wizard.pro.authorize"
|
||||||
|
action=(route-action "authorize")}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{wizard-message
|
||||||
|
key=messageKey
|
||||||
|
url=messageUrl
|
||||||
|
type=messageType
|
||||||
|
opts=messageOpts
|
||||||
|
component="pro"}}
|
||||||
|
|
||||||
|
<div class="admin-wizard-container">
|
||||||
|
{{#if showSubscription}}
|
||||||
|
{{wizard-pro-subscription subscription=model.subscription}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
|
@ -7,6 +7,7 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{nav-item route="adminWizardsLogs" label="admin.wizard.log.nav_label"}}
|
{{nav-item route="adminWizardsLogs" label="admin.wizard.log.nav_label"}}
|
||||||
{{nav-item route="adminWizardsManager" label="admin.wizard.manager.nav_label"}}
|
{{nav-item route="adminWizardsManager" label="admin.wizard.manager.nav_label"}}
|
||||||
|
{{nav-item route="adminWizardsPro" label="admin.wizard.pro.nav_label"}}
|
||||||
{{/admin-nav}}
|
{{/admin-nav}}
|
||||||
|
|
||||||
<div class="admin-container">
|
<div class="admin-container">
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
<div class="title-container">
|
||||||
|
<h3 class="subscription-title">{{title}}</h3>
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
<span>
|
||||||
|
{{#if updating}}
|
||||||
|
{{loading-spinner size="small"}}
|
||||||
|
{{else if updateIcon}}
|
||||||
|
{{d-icon updateIcon}}
|
||||||
|
{{/if}}
|
||||||
|
</span>
|
||||||
|
{{d-button
|
||||||
|
icon="sync"
|
||||||
|
action=(action "update")
|
||||||
|
disabled=updating
|
||||||
|
label="admin.wizard.pro.subscription.update"}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#if subscribed}}
|
||||||
|
<div class="detail-container">
|
||||||
|
<div class="subscription-state {{stateClass}}">{{stateLabel}}</div>
|
||||||
|
<div class="subscription-updated-at">
|
||||||
|
{{{i18n
|
||||||
|
'admin.wizard.pro.subscription.last_updated'
|
||||||
|
updated_at=(format-date subscription.updated_at leaveAgo="true")
|
||||||
|
}}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
|
@ -7,7 +7,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 10px;
|
||||||
|
|
||||||
& + .wizard-message + div {
|
& + .wizard-message + div {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
@ -715,3 +715,61 @@
|
||||||
width: 80px;
|
width: 80px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-wizards-pro {
|
||||||
|
.admin-wizard-controls {
|
||||||
|
h3, label {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
padding: .4em .5em;
|
||||||
|
margin-left: .75em;
|
||||||
|
background-color: $success;
|
||||||
|
color: $secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-wizard-pro-subscription {
|
||||||
|
.title-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: .5em;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons > span {
|
||||||
|
margin-right: .5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1em;
|
||||||
|
background-color: $primary-very-low;
|
||||||
|
|
||||||
|
.subscription-state {
|
||||||
|
padding: .25em .5em;
|
||||||
|
margin-right: .75em;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: $success;
|
||||||
|
color: $secondary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -86,12 +86,20 @@ en:
|
||||||
no_file: Please choose a file to import
|
no_file: Please choose a file to import
|
||||||
file_size_error: The file size must be 512kb or less
|
file_size_error: The file size must be 512kb or less
|
||||||
file_format_error: The file must be a .json file
|
file_format_error: The file must be a .json file
|
||||||
server_error: "Error: {{message}}"
|
|
||||||
importing: Importing wizards...
|
importing: Importing wizards...
|
||||||
destroying: Destroying wizards...
|
destroying: Destroying wizards...
|
||||||
import_complete: Import complete
|
import_complete: Import complete
|
||||||
destroy_complete: Destruction complete
|
destroy_complete: Destruction complete
|
||||||
|
pro:
|
||||||
|
documentation: Check out the PRO documentation
|
||||||
|
authorize: "Authorize this forum to use your PRO subscription plan on %{server}."
|
||||||
|
not_subscribed: "You've authorized, but are not currently subscribed to a PRO plan on %{server}."
|
||||||
|
subscription_expiring: "Your subscription is active, but will expire in the next 48 hours."
|
||||||
|
subscription_active: "Your subscription is active."
|
||||||
|
subscription_inactive: "Your subscription is inactive on this forum. Read more in <a href='https://thepavilion.io/t/3652'>the documentation</a>."
|
||||||
|
unauthorized: "You're unauthorized. If you have a subscription, it will become inactive in the next 48 hours."
|
||||||
|
unauthorize_failed: Failed to unauthorize.
|
||||||
|
|
||||||
editor:
|
editor:
|
||||||
show: "Show"
|
show: "Show"
|
||||||
hide: "Hide"
|
hide: "Hide"
|
||||||
|
@ -424,7 +432,24 @@ en:
|
||||||
imported: imported
|
imported: imported
|
||||||
upload: Select wizards.json
|
upload: Select wizards.json
|
||||||
destroy: Destroy
|
destroy: Destroy
|
||||||
destroyed: destroyed
|
destroyed: destroyed
|
||||||
|
|
||||||
|
pro:
|
||||||
|
nav_label: PRO
|
||||||
|
title: Custom Wizard PRO
|
||||||
|
authorize: Authorize
|
||||||
|
authorized: Authorized
|
||||||
|
unauthorize: cancel
|
||||||
|
not_subscribed: You're not currently subscribed
|
||||||
|
subscription:
|
||||||
|
title:
|
||||||
|
community: Community Subscription
|
||||||
|
business: Business Subscription
|
||||||
|
status:
|
||||||
|
active: Active
|
||||||
|
inactive: Inactive
|
||||||
|
update: Update
|
||||||
|
last_updated: Last updated {{updated_at}}
|
||||||
|
|
||||||
wizard_js:
|
wizard_js:
|
||||||
group:
|
group:
|
||||||
|
|
|
@ -43,5 +43,11 @@ Discourse::Application.routes.append do
|
||||||
get 'admin/wizards/manager/export' => 'admin_manager#export'
|
get 'admin/wizards/manager/export' => 'admin_manager#export'
|
||||||
post 'admin/wizards/manager/import' => 'admin_manager#import'
|
post 'admin/wizards/manager/import' => 'admin_manager#import'
|
||||||
delete 'admin/wizards/manager/destroy' => 'admin_manager#destroy'
|
delete 'admin/wizards/manager/destroy' => 'admin_manager#destroy'
|
||||||
|
|
||||||
|
get 'admin/wizards/pro' => 'admin_pro#index'
|
||||||
|
get 'admin/wizards/pro/authorize' => 'admin_pro#authorize'
|
||||||
|
get 'admin/wizards/pro/authorize/callback' => 'admin_pro#authorize_callback'
|
||||||
|
delete 'admin/wizards/pro/authorize' => 'admin_pro#destroy'
|
||||||
|
post 'admin/wizards/pro/subscription' => 'admin_pro#update_subscription'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
46
controllers/custom_wizard/admin/pro.rb
Normale Datei
46
controllers/custom_wizard/admin/pro.rb
Normale Datei
|
@ -0,0 +1,46 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CustomWizard::AdminProController < CustomWizard::AdminController
|
||||||
|
skip_before_action :check_xhr, :preload_json, :verify_authenticity_token, only: [:authorize, :authorize_callback]
|
||||||
|
|
||||||
|
def index
|
||||||
|
render_serialized(CustomWizard::Pro.new, CustomWizard::ProSerializer, root: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorize
|
||||||
|
request_id = SecureRandom.hex(32)
|
||||||
|
cookies[:user_api_request_id] = request_id
|
||||||
|
redirect_to CustomWizard::ProAuthentication.generate_request(current_user.id, request_id).to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorize_callback
|
||||||
|
payload = params[:payload]
|
||||||
|
request_id = cookies[:user_api_request_id]
|
||||||
|
|
||||||
|
CustomWizard::ProAuthentication.handle_response(request_id, payload)
|
||||||
|
CustomWizard::ProSubscription.update
|
||||||
|
|
||||||
|
redirect_to '/admin/wizards/pro'
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
if CustomWizard::ProAuthentication.destroy
|
||||||
|
render json: success_json
|
||||||
|
else
|
||||||
|
render json: failed_json
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_subscription
|
||||||
|
if CustomWizard::ProSubscription.update
|
||||||
|
render json: success_json.merge(
|
||||||
|
subscription: CustomWizard::ProSubscriptionSerializer.new(
|
||||||
|
CustomWizard::ProSubscription.new,
|
||||||
|
root: false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else
|
||||||
|
render json: failed_json
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
11
jobs/scheduled/update_pro_status.rb
Normale Datei
11
jobs/scheduled/update_pro_status.rb
Normale Datei
|
@ -0,0 +1,11 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Jobs
|
||||||
|
class UpdateProSubscription < ::Jobs::Scheduled
|
||||||
|
every 1.days
|
||||||
|
|
||||||
|
def execute(args)
|
||||||
|
CustomWizard::ProSubscription.update
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
21
lib/custom_wizard/pro.rb
Normale Datei
21
lib/custom_wizard/pro.rb
Normale Datei
|
@ -0,0 +1,21 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CustomWizard::Pro
|
||||||
|
NAMESPACE ||= "#{CustomWizard::PLUGIN_NAME}_pro"
|
||||||
|
|
||||||
|
attr_reader :authentication,
|
||||||
|
:subscription
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@authentication = CustomWizard::ProAuthentication.new
|
||||||
|
@subscription = CustomWizard::ProSubscription.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorized?
|
||||||
|
@authentication.active?
|
||||||
|
end
|
||||||
|
|
||||||
|
def subscribed?
|
||||||
|
@subscription.active?
|
||||||
|
end
|
||||||
|
end
|
155
lib/custom_wizard/pro/authentication.rb
Normale Datei
155
lib/custom_wizard/pro/authentication.rb
Normale Datei
|
@ -0,0 +1,155 @@
|
||||||
|
class CustomWizard::ProAuthentication
|
||||||
|
include ActiveModel::Serialization
|
||||||
|
|
||||||
|
API_KEY ||= "api_key"
|
||||||
|
API_CLIENT_ID ||= 'api_client_id'
|
||||||
|
KEYS ||= "keys"
|
||||||
|
|
||||||
|
attr_reader :client_id,
|
||||||
|
:auth_by,
|
||||||
|
:auth_at,
|
||||||
|
:api_key
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
api = get_api_key
|
||||||
|
|
||||||
|
@api_key = api.key
|
||||||
|
@auth_at = api.auth_at
|
||||||
|
@auth_by = api.auth_by
|
||||||
|
@client_id = get_client_id || set_client_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def active?
|
||||||
|
@api_key.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(data)
|
||||||
|
api_key = data[:key]
|
||||||
|
user_id = data[:user_id]
|
||||||
|
user = User.find(user_id)
|
||||||
|
|
||||||
|
if user&.admin
|
||||||
|
set_api_key(api_key, user.id)
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
remove
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.destroy
|
||||||
|
self.new.destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_keys(user_id, request_id)
|
||||||
|
rsa = OpenSSL::PKey::RSA.generate(2048)
|
||||||
|
nonce = SecureRandom.hex(32)
|
||||||
|
set_keys(request_id, user_id, rsa, nonce)
|
||||||
|
|
||||||
|
OpenStruct.new(nonce: nonce, public_key: rsa.public_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
def decrypt_payload(request_id, payload)
|
||||||
|
keys = get_keys(request_id)
|
||||||
|
return false unless keys.present? && keys.pem
|
||||||
|
delete_keys(request_id)
|
||||||
|
|
||||||
|
rsa = OpenSSL::PKey::RSA.new(keys.pem)
|
||||||
|
decrypted_payload = rsa.private_decrypt(Base64.decode64(payload))
|
||||||
|
return false unless decrypted_payload.present?
|
||||||
|
|
||||||
|
begin
|
||||||
|
data = JSON.parse(decrypted_payload).symbolize_keys
|
||||||
|
rescue JSON::ParserError
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return false unless data[:nonce] == keys.nonce
|
||||||
|
data[:user_id] = keys.user_id
|
||||||
|
|
||||||
|
data
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.generate_request(user_id, request_id)
|
||||||
|
authentication = self.new
|
||||||
|
keys = authentication.generate_keys(user_id, request_id)
|
||||||
|
|
||||||
|
params = {
|
||||||
|
public_key: keys.public_key,
|
||||||
|
nonce: keys.nonce,
|
||||||
|
client_id: authentication.client_id,
|
||||||
|
auth_redirect: "#{Discourse.base_url}/admin/wizards/pro/authorize/callback",
|
||||||
|
application_name: SiteSetting.title,
|
||||||
|
scopes: CustomWizard::ProSubscription::SCOPE
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = URI.parse("https://#{CustomWizard::ProSubscription::SUBSCRIPTION_SERVER}/user-api-key/new")
|
||||||
|
uri.query = URI.encode_www_form(params)
|
||||||
|
uri.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.handle_response(request_id, payload)
|
||||||
|
authentication = self.new
|
||||||
|
|
||||||
|
data = authentication.decrypt_payload(request_id, payload)
|
||||||
|
return unless data.is_a?(Hash) && data[:key] && data[:user_id]
|
||||||
|
|
||||||
|
authentication.update(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def get_api_key
|
||||||
|
raw = PluginStore.get(CustomWizard::Pro::NAMESPACE, API_KEY)
|
||||||
|
OpenStruct.new(
|
||||||
|
key: raw && raw['key'],
|
||||||
|
auth_by: raw && raw['auth_by'],
|
||||||
|
auth_at: raw && raw['auth_at']
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_api_key(key, user_id)
|
||||||
|
PluginStore.set(CustomWizard::Pro::NAMESPACE, API_KEY,
|
||||||
|
key: key,
|
||||||
|
auth_by: user_id,
|
||||||
|
auth_at: Time.now
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove
|
||||||
|
PluginStore.remove(CustomWizard::Pro::NAMESPACE, API_KEY)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_client_id
|
||||||
|
PluginStore.get(CustomWizard::Pro::NAMESPACE, API_CLIENT_ID)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_client_id
|
||||||
|
client_id = SecureRandom.hex(32)
|
||||||
|
PluginStore.set(CustomWizard::Pro::NAMESPACE, API_CLIENT_ID, client_id)
|
||||||
|
client_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_keys(request_id, user_id, rsa, nonce)
|
||||||
|
PluginStore.set(CustomWizard::Pro::NAMESPACE, "#{KEYS}_#{request_id}",
|
||||||
|
user_id: user_id,
|
||||||
|
pem: rsa.export,
|
||||||
|
nonce: nonce
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_keys(request_id)
|
||||||
|
raw = PluginStore.get(CustomWizard::Pro::NAMESPACE, "#{KEYS}_#{request_id}")
|
||||||
|
OpenStruct.new(
|
||||||
|
user_id: raw && raw['user_id'],
|
||||||
|
pem: raw && raw['pem'],
|
||||||
|
nonce: raw && raw['nonce']
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_keys(request_id)
|
||||||
|
PluginStore.remove(CustomWizard::Pro::NAMESPACE, "#{KEYS}_#{request_id}")
|
||||||
|
end
|
||||||
|
end
|
74
lib/custom_wizard/pro/subscription.rb
Normale Datei
74
lib/custom_wizard/pro/subscription.rb
Normale Datei
|
@ -0,0 +1,74 @@
|
||||||
|
class CustomWizard::ProSubscription
|
||||||
|
include ActiveModel::Serialization
|
||||||
|
|
||||||
|
SUBSCRIPTION_SERVER ||= "test.thepavilion.io"
|
||||||
|
SUBSCRIPTION_TYPE ||= "stripe"
|
||||||
|
SCOPE ||= "discourse-subscription-server:user_subscription"
|
||||||
|
CLIENT_NAME ||= "custom-wizard"
|
||||||
|
SUBSCRIPTION_KEY ||= "custom_wizard_pro_subscription"
|
||||||
|
UPDATE_DAY_BUFFER ||= 2
|
||||||
|
TYPES ||= %w(community business)
|
||||||
|
|
||||||
|
attr_reader :type,
|
||||||
|
:updated_at
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
raw = get
|
||||||
|
|
||||||
|
if raw
|
||||||
|
@type = raw['type']
|
||||||
|
@updated_at = raw['updated_at']
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def active?
|
||||||
|
TYPES.include?(type) && updated_at.to_datetime > (Date.today - UPDATE_DAY_BUFFER.days).to_datetime
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(data)
|
||||||
|
return false unless data && data.is_a?(Hash)
|
||||||
|
subscriptions = data[:subscriptions]
|
||||||
|
|
||||||
|
if subscriptions.present?
|
||||||
|
subscription = subscriptions.first
|
||||||
|
type = subscription[:price_nickname]
|
||||||
|
|
||||||
|
set(type)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.update
|
||||||
|
@subscribed = nil
|
||||||
|
auth = CustomWizard::ProAuthentication.new
|
||||||
|
subscription = self.new
|
||||||
|
|
||||||
|
if auth.active?
|
||||||
|
response = Excon.get(
|
||||||
|
"https://#{SUBSCRIPTION_SERVER}/subscription-server/user-subscriptions/#{SUBSCRIPTION_TYPE}/#{CLIENT_NAME}",
|
||||||
|
headers: { "User-Api-Key" => auth.api_key }
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status == 200
|
||||||
|
begin
|
||||||
|
data = JSON.parse(response.body).deep_symbolize_keys
|
||||||
|
rescue JSON::ParserError
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return subscription.update(data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set(type)
|
||||||
|
PluginStore.set(CustomWizard::Pro::NAMESPACE, SUBSCRIPTION_KEY, type: type, updated_at: Time.now)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get
|
||||||
|
PluginStore.get(CustomWizard::Pro::NAMESPACE, SUBSCRIPTION_KEY)
|
||||||
|
end
|
||||||
|
end
|
12
plugin.rb
12
plugin.rb
|
@ -62,11 +62,13 @@ after_initialize do
|
||||||
../controllers/custom_wizard/admin/logs.rb
|
../controllers/custom_wizard/admin/logs.rb
|
||||||
../controllers/custom_wizard/admin/manager.rb
|
../controllers/custom_wizard/admin/manager.rb
|
||||||
../controllers/custom_wizard/admin/custom_fields.rb
|
../controllers/custom_wizard/admin/custom_fields.rb
|
||||||
|
../controllers/custom_wizard/admin/pro.rb
|
||||||
../controllers/custom_wizard/wizard.rb
|
../controllers/custom_wizard/wizard.rb
|
||||||
../controllers/custom_wizard/steps.rb
|
../controllers/custom_wizard/steps.rb
|
||||||
../controllers/custom_wizard/realtime_validations.rb
|
../controllers/custom_wizard/realtime_validations.rb
|
||||||
../jobs/refresh_api_access_token.rb
|
../jobs/regular/refresh_api_access_token.rb
|
||||||
../jobs/set_after_time_wizard.rb
|
../jobs/regular/set_after_time_wizard.rb
|
||||||
|
../jobs/scheduled/update_pro_status.rb
|
||||||
../lib/custom_wizard/validators/template.rb
|
../lib/custom_wizard/validators/template.rb
|
||||||
../lib/custom_wizard/validators/update.rb
|
../lib/custom_wizard/validators/update.rb
|
||||||
../lib/custom_wizard/action_result.rb
|
../lib/custom_wizard/action_result.rb
|
||||||
|
@ -85,6 +87,9 @@ after_initialize do
|
||||||
../lib/custom_wizard/submission.rb
|
../lib/custom_wizard/submission.rb
|
||||||
../lib/custom_wizard/template.rb
|
../lib/custom_wizard/template.rb
|
||||||
../lib/custom_wizard/wizard.rb
|
../lib/custom_wizard/wizard.rb
|
||||||
|
../lib/custom_wizard/pro.rb
|
||||||
|
../lib/custom_wizard/pro/subscription.rb
|
||||||
|
../lib/custom_wizard/pro/authentication.rb
|
||||||
../lib/custom_wizard/api/api.rb
|
../lib/custom_wizard/api/api.rb
|
||||||
../lib/custom_wizard/api/authorization.rb
|
../lib/custom_wizard/api/authorization.rb
|
||||||
../lib/custom_wizard/api/endpoint.rb
|
../lib/custom_wizard/api/endpoint.rb
|
||||||
|
@ -105,6 +110,9 @@ after_initialize do
|
||||||
../serializers/custom_wizard/log_serializer.rb
|
../serializers/custom_wizard/log_serializer.rb
|
||||||
../serializers/custom_wizard/submission_serializer.rb
|
../serializers/custom_wizard/submission_serializer.rb
|
||||||
../serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb
|
../serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb
|
||||||
|
../serializers/custom_wizard/pro_serializer.rb
|
||||||
|
../serializers/custom_wizard/pro/authentication_serializer.rb
|
||||||
|
../serializers/custom_wizard/pro/subscription_serializer.rb
|
||||||
../extensions/extra_locales_controller.rb
|
../extensions/extra_locales_controller.rb
|
||||||
../extensions/invites_controller.rb
|
../extensions/invites_controller.rb
|
||||||
../extensions/users_controller.rb
|
../extensions/users_controller.rb
|
||||||
|
|
11
serializers/custom_wizard/pro/authentication_serializer.rb
Normale Datei
11
serializers/custom_wizard/pro/authentication_serializer.rb
Normale Datei
|
@ -0,0 +1,11 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
class CustomWizard::ProAuthenticationSerializer < ApplicationSerializer
|
||||||
|
attributes :active,
|
||||||
|
:client_id,
|
||||||
|
:auth_by,
|
||||||
|
:auth_at
|
||||||
|
|
||||||
|
def active
|
||||||
|
object.active?
|
||||||
|
end
|
||||||
|
end
|
10
serializers/custom_wizard/pro/subscription_serializer.rb
Normale Datei
10
serializers/custom_wizard/pro/subscription_serializer.rb
Normale Datei
|
@ -0,0 +1,10 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
class CustomWizard::ProSubscriptionSerializer < ApplicationSerializer
|
||||||
|
attributes :type,
|
||||||
|
:active,
|
||||||
|
:updated_at
|
||||||
|
|
||||||
|
def active
|
||||||
|
object.active?
|
||||||
|
end
|
||||||
|
end
|
26
serializers/custom_wizard/pro_serializer.rb
Normale Datei
26
serializers/custom_wizard/pro_serializer.rb
Normale Datei
|
@ -0,0 +1,26 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
class CustomWizard::ProSerializer < ApplicationSerializer
|
||||||
|
attributes :server,
|
||||||
|
:authentication,
|
||||||
|
:subscription
|
||||||
|
|
||||||
|
def server
|
||||||
|
CustomWizard::ProSubscription::SUBSCRIPTION_SERVER
|
||||||
|
end
|
||||||
|
|
||||||
|
def authentication
|
||||||
|
if object.authentication
|
||||||
|
CustomWizard::ProAuthenticationSerializer.new(object.authentication, root: false)
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def subscription
|
||||||
|
if object.subscription
|
||||||
|
CustomWizard::ProSubscriptionSerializer.new(object.subscription, root: false)
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Laden …
In neuem Issue referenzieren