├── .editorconfig
├── .gitignore
├── .styleci.yml
├── LICENSE
├── README.md
├── composer.json
├── extend.php
├── js
├── admin.js
├── dist
│ ├── admin.js
│ ├── admin.js.map
│ ├── forum.js
│ └── forum.js.map
├── forum.js
├── package.json
├── src
│ ├── admin
│ │ ├── components
│ │ │ └── SSOSettingsModal.js
│ │ └── index.js
│ └── forum
│ │ └── index.js
└── webpack.config.js
├── locale
├── en.yml
└── pl.yml
├── sample-website
├── Forum.php
├── config.php.dist
├── index.php
├── logout.php
└── wordpress-flarum-sso.php
└── src
└── Listener
├── ActivateUser.php
├── AddClientAssets.php
├── AddLogoutRedirect.php
└── LoadSettingsFromDatabase.php
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 | indent_style = space
13 | indent_size = 2
14 |
15 | [*.{diff,md}]
16 | trim_trailing_whitespace = false
17 |
18 | [*.php]
19 | indent_size = 4
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | sample-website/config.php
--------------------------------------------------------------------------------
/.styleci.yml:
--------------------------------------------------------------------------------
1 | preset: recommended
2 |
3 | enabled:
4 | - logical_not_operators_with_successor_space
5 |
6 | disabled:
7 | - align_double_arrow
8 | - multiline_array_trailing_comma
9 | - new_with_braces
10 | - phpdoc_align
11 | - phpdoc_order
12 | - phpdoc_separation
13 | - phpdoc_types
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Fabian Wüthrich
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DEPRECATED - Flarum Single Sign On
2 |
3 | (no longer actively maintained)
4 |
5 | This extension equips Flarum with Single Sign On. The workflow is based on this
6 | [post](https://discuss.flarum.org/d/2808-how-i-implemented-cross-authentication-with-flarum).
7 | The extension is useful if you run Flarum on a subdomain but you want to use the login mechanism
8 | of your main website. A dummy main website is provided in the `sample-website/` folder.
9 |
10 | ## Installation
11 |
12 | 1. Create a random token and put it into the `api_keys` table of your Flarum database.
13 |
14 | 2. Go into `sample-website` folder and copy `config.php.dist` to `config.php`:
15 | ```
16 | cd sample-website/
17 | cp config.php.dist config.php
18 | ```
19 | 3. Open `config.php` with an editor of your choice and configure all settings.
20 |
21 | 4. Upload the `Forum.php` class and `config.php` to your main website and setup the `Forum.php` class. An example is given in `index.php` / `logout.php`.
22 |
23 | 5. Install and activate the extension. Fill in redirect urls for login, signup and logout.
24 | ```
25 | composer require wuethrich44/flarum-ext-sso
26 | ```
27 | 6. Now you should able to log in with your existing users.
28 |
29 | ## Wordpress
30 |
31 | This extension comes with a Wordpress plugin which allows you to login into Wordpress and gain also access to your Flarum
32 | forum. In order to install the plugin execute the following steps:
33 |
34 | 1. Upload the `sample-website` folder into the plugin folder (`/wp-content/plugins/`) of your wordpress instance.
35 |
36 | 2. Rename it to a name of your choice (e.g. `flarum-sso`).
37 |
38 | 3. Copy `config.php.dist` to `config.php` and configure all settings.
39 |
40 | 4. Activate the plugin in the settings.
41 |
42 | 5. Install and activate the Flarum extension.
43 | ```
44 | composer require wuethrich44/flarum-ext-sso
45 | ```
46 |
47 | 6. Fill in the correct urls according to your wordpress instance:
48 |
49 | **Login-Url**: `http://example.com/wp-login.php?redirect_to=forum`
50 |
51 | (The `redirect_to=forum` part is important as it will redirect your users back to the forum)
52 |
53 | **Logout-Url**: `http://example.com/wp-login.php?action=logout`
54 |
55 | **Signup-Url**: Depending on which plugin you use.
56 |
57 | 7. That's it!
58 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wuethrich44/flarum-ext-sso",
3 | "description": "Single Sign On for Flarum",
4 | "type": "flarum-extension",
5 | "keywords": [
6 | "authentication"
7 | ],
8 | "license": "MIT",
9 | "authors": [
10 | {
11 | "name": "Fabian Wüthrich",
12 | "email": "fabian.wuethrich@outlook.com"
13 | }
14 | ],
15 | "support": {
16 | "issues": "https://github.com/wuethrich44/flarum-ext-sso/issues",
17 | "source": "https://github.com/wuethrich44/flarum-ext-sso"
18 | },
19 | "require": {
20 | "flarum/core": "^0.1.0-beta.8.1"
21 | },
22 | "autoload": {
23 | "psr-4": {
24 | "Wuethrich44\\SSO\\": "src/"
25 | }
26 | },
27 | "extra": {
28 | "flarum-extension": {
29 | "title": "Single Sign On",
30 | "icon": {
31 | "name": "shield"
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/extend.php:
--------------------------------------------------------------------------------
1 | js(__DIR__ . '/js/dist/forum.js'),
10 |
11 | (new Extend\Frontend('admin'))
12 | ->js(__DIR__ . '/js/dist/admin.js'),
13 |
14 | function (Dispatcher $events) {
15 | $events->subscribe(Listener\AddLogoutRedirect::class);
16 | $events->subscribe(Listener\ActivateUser::class);
17 | $events->subscribe(Listener\LoadSettingsFromDatabase::class);
18 | },
19 |
20 | new Extend\Locales(__DIR__ . '/locale'),
21 | ];
22 |
--------------------------------------------------------------------------------
/js/admin.js:
--------------------------------------------------------------------------------
1 | export * from './src/admin';
2 |
--------------------------------------------------------------------------------
/js/dist/admin.js:
--------------------------------------------------------------------------------
1 | module.exports=function(t){var r={};function e(n){if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{}};return t[n].call(o.exports,o,o.exports,e),o.l=!0,o.exports}return e.m=t,e.c=r,e.d=function(t,r,n){e.o(t,r)||Object.defineProperty(t,r,{enumerable:!0,get:n})},e.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},e.t=function(t,r){if(1&r&&(t=e(t)),8&r)return t;if(4&r&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(e.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&r&&"string"!=typeof t)for(var o in t)e.d(n,o,function(r){return t[r]}.bind(null,o));return n},e.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(r,"a",r),r},e.o=function(t,r){return Object.prototype.hasOwnProperty.call(t,r)},e.p="",e(e.s=6)}([function(t,r){t.exports=flarum.core.compat.app},,,,,function(t,r){t.exports=flarum.core.compat["components/SettingsModal"]},function(t,r,e){"use strict";e.r(r);var n=e(0),o=e.n(n);var i=e(5),u=function(t){var r,e;function n(){return t.apply(this,arguments)||this}e=t,(r=n).prototype=Object.create(e.prototype),r.prototype.constructor=r,r.__proto__=e;var o=n.prototype;return o.className=function(){return"Modal--small"},o.title=function(){return app.translator.trans("wuethrich44-sso.admin.settings.title")},o.form=function(){return[m("div",{className:"Form-group"},m("label",null,app.translator.trans("wuethrich44-sso.admin.settings.signup_url")),m("input",{className:"FormControl",bidi:this.setting("wuethrich44-sso.signup_url")})),m("div",{className:"Form-group"},m("label",null,app.translator.trans("wuethrich44-sso.admin.settings.login_url")),m("input",{className:"FormControl",bidi:this.setting("wuethrich44-sso.login_url")})),m("div",{className:"Form-group"},m("label",null,app.translator.trans("wuethrich44-sso.admin.settings.logout_url")),m("input",{className:"FormControl",bidi:this.setting("wuethrich44-sso.logout_url")}))]},n}(e.n(i).a);o.a.initializers.add("wuethrich44-sso",function(){o.a.extensionSettings["wuethrich44-sso"]=function(){return o.a.modal.show(new u)}})}]);
2 | //# sourceMappingURL=admin.js.map
--------------------------------------------------------------------------------
/js/dist/admin.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["webpack://@wuethrich44/flarum-ext-sso/webpack/bootstrap","webpack://@wuethrich44/flarum-ext-sso/external \"flarum.core.compat['app']\"","webpack://@wuethrich44/flarum-ext-sso/external \"flarum.core.compat['components/SettingsModal']\"","webpack://@wuethrich44/flarum-ext-sso/./src/admin/components/SSOSettingsModal.js","webpack://@wuethrich44/flarum-ext-sso/./node_modules/@babel/runtime/helpers/esm/inheritsLoose.js","webpack://@wuethrich44/flarum-ext-sso/./src/admin/index.js"],"names":["installedModules","__webpack_require__","moduleId","exports","module","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","flarum","core","compat","SSOSettingsModal","subClass","superClass","constructor","__proto__","className","title","app","translator","trans","form","bidi","this","setting","SettingsModal","initializers","add","extensionSettings","modal","show"],"mappings":"2BACA,IAAAA,EAAA,GAGA,SAAAC,EAAAC,GAGA,GAAAF,EAAAE,GACA,OAAAF,EAAAE,GAAAC,QAGA,IAAAC,EAAAJ,EAAAE,GAAA,CACAG,EAAAH,EACAI,GAAA,EACAH,QAAA,IAUA,OANAI,EAAAL,GAAAM,KAAAJ,EAAAD,QAAAC,IAAAD,QAAAF,GAGAG,EAAAE,GAAA,EAGAF,EAAAD,QA0DA,OArDAF,EAAAQ,EAAAF,EAGAN,EAAAS,EAAAV,EAGAC,EAAAU,EAAA,SAAAR,EAAAS,EAAAC,GACAZ,EAAAa,EAAAX,EAAAS,IACAG,OAAAC,eAAAb,EAAAS,EAAA,CAA0CK,YAAA,EAAAC,IAAAL,KAK1CZ,EAAAkB,EAAA,SAAAhB,GACA,oBAAAiB,eAAAC,aACAN,OAAAC,eAAAb,EAAAiB,OAAAC,YAAA,CAAwDC,MAAA,WAExDP,OAAAC,eAAAb,EAAA,cAAiDmB,OAAA,KAQjDrB,EAAAsB,EAAA,SAAAD,EAAAE,GAEA,GADA,EAAAA,IAAAF,EAAArB,EAAAqB,IACA,EAAAE,EAAA,OAAAF,EACA,KAAAE,GAAA,iBAAAF,QAAAG,WAAA,OAAAH,EACA,IAAAI,EAAAX,OAAAY,OAAA,MAGA,GAFA1B,EAAAkB,EAAAO,GACAX,OAAAC,eAAAU,EAAA,WAAyCT,YAAA,EAAAK,UACzC,EAAAE,GAAA,iBAAAF,EAAA,QAAAM,KAAAN,EAAArB,EAAAU,EAAAe,EAAAE,EAAA,SAAAA,GAAgH,OAAAN,EAAAM,IAAqBC,KAAA,KAAAD,IACrI,OAAAF,GAIAzB,EAAA6B,EAAA,SAAA1B,GACA,IAAAS,EAAAT,KAAAqB,WACA,WAA2B,OAAArB,EAAA,SAC3B,WAAiC,OAAAA,GAEjC,OADAH,EAAAU,EAAAE,EAAA,IAAAA,GACAA,GAIAZ,EAAAa,EAAA,SAAAiB,EAAAC,GAAsD,OAAAjB,OAAAkB,UAAAC,eAAA1B,KAAAuB,EAAAC,IAGtD/B,EAAAkC,EAAA,GAIAlC,IAAAmC,EAAA,mBClFAhC,EAAAD,QAAAkC,OAAAC,KAAAC,OAAA,uBCAAnC,EAAAD,QAAAkC,OAAAC,KAAAC,OAAA,gGCEqBC,cCFN,IAAAC,EAAAC,yDAAAD,KACfR,UAAAlB,OAAAY,OAAAe,EAAAT,WACAQ,EAAAR,UAAAU,YAAAF,EACAA,EAAAG,UAAAF,6BDAIG,UAAA,WACI,MAAO,kBAGXC,MAAA,WACI,OAAOC,IAAIC,WAAWC,MAAM,2CAGhCC,KAAA,WACI,MAAO,CACHzC,EAAA,OAAKoC,UAAU,cACXpC,EAAA,aAAQsC,IAAIC,WAAWC,MAAM,8CAC7BxC,EAAA,SAAOoC,UAAU,cAAcM,KAAMC,KAAKC,QAAQ,iCAEtD5C,EAAA,OAAKoC,UAAU,cACXpC,EAAA,aAAQsC,IAAIC,WAAWC,MAAM,6CAC7BxC,EAAA,SAAOoC,UAAU,cAAcM,KAAMC,KAAKC,QAAQ,gCAEtD5C,EAAA,OAAKoC,UAAU,cACXpC,EAAA,aAAQsC,IAAIC,WAAWC,MAAM,8CAC7BxC,EAAA,SAAOoC,UAAU,cAAcM,KAAMC,KAAKC,QAAQ,6CArBpBC,GEC9CP,IAAIQ,aAAaC,IAAI,kBAAmB,WACpCT,IAAIU,kBAAkB,mBAAqB,kBAAMV,IAAIW,MAAMC,KAAK,IAAInB","file":"admin.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 6);\n","module.exports = flarum.core.compat['app'];","module.exports = flarum.core.compat['components/SettingsModal'];","import SettingsModal from \"flarum/components/SettingsModal\";\n\nexport default class SSOSettingsModal extends SettingsModal {\n className() {\n return 'Modal--small';\n }\n\n title() {\n return app.translator.trans('wuethrich44-sso.admin.settings.title');\n }\n\n form() {\n return [\n
\n \n \n
,\n \n \n \n
,\n \n \n \n
\n ];\n }\n}","export default function _inheritsLoose(subClass, superClass) {\n subClass.prototype = Object.create(superClass.prototype);\n subClass.prototype.constructor = subClass;\n subClass.__proto__ = superClass;\n}","import app from \"flarum/app\";\nimport SSOSettingsModal from \"./components/SSOSettingsModal\";\n\napp.initializers.add('wuethrich44-sso', () => {\n app.extensionSettings['wuethrich44-sso'] = () => app.modal.show(new SSOSettingsModal());\n});\n"],"sourceRoot":""}
--------------------------------------------------------------------------------
/js/dist/forum.js:
--------------------------------------------------------------------------------
1 | module.exports=function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(r,o,function(e){return t[e]}.bind(null,o));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=7)}([function(t,e){t.exports=flarum.core.compat.app},function(t,e){t.exports=flarum.core.compat.extend},function(t,e){t.exports=flarum.core.compat["components/HeaderSecondary"]},function(t,e){t.exports=flarum.core.compat["components/SettingsPage"]},function(t,e){t.exports=flarum.core.compat["components/LogInModal"]},,,function(t,e,n){"use strict";n.r(e);var r=n(1),o=n(0),a=n.n(o),u=n(2),c=n.n(u),i=n(3),s=n.n(i),l=n(4),f=n.n(l);a.a.initializers.add("wuethrich44-sso",function(){Object(r.override)(f.a.prototype,"init",function(){throw window.location.href=a.a.forum.data.attributes["wuethrich44-sso.login_url"],new Error("Stop execution")}),Object(r.extend)(c.a.prototype,"items",function(t){if(!t.has("logIn"))return;var e=a.a.forum.data.attributes["wuethrich44-sso.login_url"];t.replace("logIn",m("a",{href:e,className:"Button Button--link"},a.a.translator.trans("core.forum.header.log_in_link")))}),Object(r.extend)(c.a.prototype,"items",function(t){if(!t.has("signUp"))return;var e=a.a.forum.data.attributes["wuethrich44-sso.signup_url"];t.replace("signUp",m("a",{href:e,className:"Button Button--link"},a.a.translator.trans("core.forum.header.sign_up_link")))}),Object(r.extend)(s.a.prototype,"accountItems",function(t){t.remove("changeEmail"),t.remove("changePassword")}),Object(r.extend)(s.a.prototype,"settingsItems",function(t){t.has("account")&&0===t.get("account").props.children.length&&t.remove("account")})})}]);
2 | //# sourceMappingURL=forum.js.map
--------------------------------------------------------------------------------
/js/dist/forum.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["webpack://@wuethrich44/flarum-ext-sso/webpack/bootstrap","webpack://@wuethrich44/flarum-ext-sso/external \"flarum.core.compat['app']\"","webpack://@wuethrich44/flarum-ext-sso/external \"flarum.core.compat['extend']\"","webpack://@wuethrich44/flarum-ext-sso/external \"flarum.core.compat['components/HeaderSecondary']\"","webpack://@wuethrich44/flarum-ext-sso/external \"flarum.core.compat['components/SettingsPage']\"","webpack://@wuethrich44/flarum-ext-sso/external \"flarum.core.compat['components/LogInModal']\"","webpack://@wuethrich44/flarum-ext-sso/./src/forum/index.js"],"names":["installedModules","__webpack_require__","moduleId","exports","module","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","flarum","core","compat","app","initializers","add","override","LogInModal","window","location","href","forum","data","attributes","Error","extend","HeaderSecondary","items","has","loginUrl","replace","className","translator","trans","signupUrl","SettingsPage","remove","props","children","length"],"mappings":"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","file":"forum.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 7);\n","module.exports = flarum.core.compat['app'];","module.exports = flarum.core.compat['extend'];","module.exports = flarum.core.compat['components/HeaderSecondary'];","module.exports = flarum.core.compat['components/SettingsPage'];","module.exports = flarum.core.compat['components/LogInModal'];","import {extend, override} from \"flarum/extend\";\nimport app from \"flarum/app\";\nimport HeaderSecondary from \"flarum/components/HeaderSecondary\";\nimport SettingsPage from \"flarum/components/SettingsPage\";\nimport LogInModal from \"flarum/components/LogInModal\";\n\napp.initializers.add('wuethrich44-sso', function () {\n override(LogInModal.prototype, 'init', redirectWhenLoginModalIsOpened);\n\n extend(HeaderSecondary.prototype, 'items', replaceLoginButton);\n extend(HeaderSecondary.prototype, 'items', replaceSignupButton);\n\n extend(SettingsPage.prototype, 'accountItems', removeProfileActions);\n extend(SettingsPage.prototype, 'settingsItems', checkRemoveAccountSection);\n\n function redirectWhenLoginModalIsOpened() {\n window.location.href = app.forum.data.attributes['wuethrich44-sso.login_url'];\n throw new Error('Stop execution');\n }\n\n function replaceLoginButton(items) {\n if (!items.has('logIn')) {\n return;\n }\n\n let loginUrl = app.forum.data.attributes['wuethrich44-sso.login_url'];\n\n items.replace('logIn',\n \n {app.translator.trans('core.forum.header.log_in_link')}\n \n );\n }\n\n function replaceSignupButton(items) {\n if (!items.has('signUp')) {\n return;\n }\n\n let signupUrl = app.forum.data.attributes['wuethrich44-sso.signup_url'];\n\n items.replace('signUp',\n \n {app.translator.trans('core.forum.header.sign_up_link')}\n \n );\n }\n\n function removeProfileActions(items) {\n items.remove('changeEmail');\n items.remove('changePassword');\n }\n\n function checkRemoveAccountSection(items) {\n if (items.has('account')\n && items.get('account').props.children.length === 0) {\n items.remove('account');\n }\n }\n});\n"],"sourceRoot":""}
--------------------------------------------------------------------------------
/js/forum.js:
--------------------------------------------------------------------------------
1 | export * from './src/forum';
2 |
--------------------------------------------------------------------------------
/js/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@wuethrich44/flarum-ext-sso",
3 | "version": "0.0.0",
4 | "dependencies": {
5 | "flarum-webpack-config": "^0.1.0-beta.8",
6 | "webpack": "^4.0.0",
7 | "webpack-cli": "^3.0.7"
8 | },
9 | "scripts": {
10 | "build": "webpack --mode production --progress",
11 | "watch": "webpack --mode development --watch"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/js/src/admin/components/SSOSettingsModal.js:
--------------------------------------------------------------------------------
1 | import SettingsModal from "flarum/components/SettingsModal";
2 |
3 | export default class SSOSettingsModal extends SettingsModal {
4 | className() {
5 | return 'Modal--small';
6 | }
7 |
8 | title() {
9 | return app.translator.trans('wuethrich44-sso.admin.settings.title');
10 | }
11 |
12 | form() {
13 | return [
14 |
15 |
16 |
17 |
,
18 |
19 |
20 |
21 |
,
22 |
23 |
24 |
25 |
26 | ];
27 | }
28 | }
--------------------------------------------------------------------------------
/js/src/admin/index.js:
--------------------------------------------------------------------------------
1 | import app from "flarum/app";
2 | import SSOSettingsModal from "./components/SSOSettingsModal";
3 |
4 | app.initializers.add('wuethrich44-sso', () => {
5 | app.extensionSettings['wuethrich44-sso'] = () => app.modal.show(new SSOSettingsModal());
6 | });
7 |
--------------------------------------------------------------------------------
/js/src/forum/index.js:
--------------------------------------------------------------------------------
1 | import {extend, override} from "flarum/extend";
2 | import app from "flarum/app";
3 | import HeaderSecondary from "flarum/components/HeaderSecondary";
4 | import SettingsPage from "flarum/components/SettingsPage";
5 | import LogInModal from "flarum/components/LogInModal";
6 |
7 | app.initializers.add('wuethrich44-sso', function () {
8 | override(LogInModal.prototype, 'init', redirectWhenLoginModalIsOpened);
9 |
10 | extend(HeaderSecondary.prototype, 'items', replaceLoginButton);
11 | extend(HeaderSecondary.prototype, 'items', replaceSignupButton);
12 |
13 | extend(SettingsPage.prototype, 'accountItems', removeProfileActions);
14 | extend(SettingsPage.prototype, 'settingsItems', checkRemoveAccountSection);
15 |
16 | function redirectWhenLoginModalIsOpened() {
17 | window.location.href = app.forum.data.attributes['wuethrich44-sso.login_url'];
18 | throw new Error('Stop execution');
19 | }
20 |
21 | function replaceLoginButton(items) {
22 | if (!items.has('logIn')) {
23 | return;
24 | }
25 |
26 | let loginUrl = app.forum.data.attributes['wuethrich44-sso.login_url'];
27 |
28 | items.replace('logIn',
29 |
30 | {app.translator.trans('core.forum.header.log_in_link')}
31 |
32 | );
33 | }
34 |
35 | function replaceSignupButton(items) {
36 | if (!items.has('signUp')) {
37 | return;
38 | }
39 |
40 | let signupUrl = app.forum.data.attributes['wuethrich44-sso.signup_url'];
41 |
42 | items.replace('signUp',
43 |
44 | {app.translator.trans('core.forum.header.sign_up_link')}
45 |
46 | );
47 | }
48 |
49 | function removeProfileActions(items) {
50 | items.remove('changeEmail');
51 | items.remove('changePassword');
52 | }
53 |
54 | function checkRemoveAccountSection(items) {
55 | if (items.has('account')
56 | && items.get('account').props.children.length === 0) {
57 | items.remove('account');
58 | }
59 | }
60 | });
61 |
--------------------------------------------------------------------------------
/js/webpack.config.js:
--------------------------------------------------------------------------------
1 | const config = require('flarum-webpack-config');
2 |
3 | module.exports = config();
4 |
--------------------------------------------------------------------------------
/locale/en.yml:
--------------------------------------------------------------------------------
1 | wuethrich44-sso:
2 | admin:
3 | settings:
4 | title: "Single Sign On Settings"
5 | signup_url: "Signup Url"
6 | login_url: "Login Url"
7 | logout_url: "Logout Url"
--------------------------------------------------------------------------------
/locale/pl.yml:
--------------------------------------------------------------------------------
1 | wuethrich44-sso:
2 | admin:
3 | settings:
4 | title: "Ustawienia logowania pojedynczego"
5 | signup_url: "URL Rejestracji"
6 | login_url: "URL Zaloguj"
7 | logout_url: "URL Wyloguj"
8 |
--------------------------------------------------------------------------------
/sample-website/Forum.php:
--------------------------------------------------------------------------------
1 | config = require __DIR__ . '/config.php';
12 | }
13 |
14 | /**
15 | * Call this method after your user is successfully authenticated.
16 | *
17 | * @param $username
18 | * @param $email
19 | */
20 | public function login($username, $email)
21 | {
22 | $password = $this->createPassword($username);
23 | $token = $this->getToken($username, $password);
24 |
25 | if (empty($token)) {
26 | $this->signup($username, $password, $email);
27 | $token = $this->getToken($username, $password);
28 | }
29 |
30 | $this->setRememberMeCookie($token);
31 | }
32 |
33 | /**
34 | * Call this method after you logged out your user.
35 | */
36 | public function logout()
37 | {
38 | $this->removeRememberMeCookie();
39 | }
40 |
41 | /**
42 | * Redirects a user back to the forum.
43 | */
44 | public function redirectToForum()
45 | {
46 | header('Location: ' . $this->config['flarum_url']);
47 | die();
48 | }
49 |
50 | private function createPassword($username)
51 | {
52 | return hash('sha256', $username . $this->config['password_token']);
53 | }
54 |
55 | private function getToken($username, $password)
56 | {
57 | $data = [
58 | 'identification' => $username,
59 | 'password' => $password,
60 | 'lifetime' => $this->getLifetimeInSeconds(),
61 | ];
62 |
63 | $response = $this->sendPostRequest('/api/token', $data);
64 |
65 | return isset($response['token']) ? $response['token'] : '';
66 | }
67 |
68 | private function signup($username, $password, $email)
69 | {
70 | $data = [
71 | "data" => [
72 | "type" => "users",
73 | "attributes" => [
74 | "username" => $username,
75 | "password" => $password,
76 | "email" => $email,
77 | ]
78 | ]
79 | ];
80 |
81 | $response = $this->sendPostRequest('/api/users', $data);
82 |
83 | return isset($response['data']['id']);
84 | }
85 |
86 | private function sendPostRequest($path, $data)
87 | {
88 | $data_string = json_encode($data);
89 |
90 | $ch = curl_init($this->config['flarum_url'] . $path);
91 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
92 | curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
93 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
94 | curl_setopt($ch, CURLOPT_HTTPHEADER, [
95 | 'Content-Type: application/json',
96 | 'Content-Length: ' . strlen($data_string),
97 | 'Authorization: Token ' . $this->config['flarum_api_key'] . '; userId=1',
98 | ]
99 | );
100 | $result = curl_exec($ch);
101 |
102 | return json_decode($result, true);
103 | }
104 |
105 | private function setRememberMeCookie($token)
106 | {
107 | $this->setCookie(self::REMEMBER_ME_KEY, $token, time() + $this->getLifetimeInSeconds());
108 | }
109 |
110 | private function removeRememberMeCookie()
111 | {
112 | unset($_COOKIE[self::REMEMBER_ME_KEY]);
113 | $this->setCookie(self::REMEMBER_ME_KEY, '', time() - 10);
114 | }
115 |
116 | private function setCookie($key, $token, $time)
117 | {
118 | setcookie($key, $token, $time, '/', $this->config['root_domain']);
119 | }
120 |
121 | private function getLifetimeInSeconds()
122 | {
123 | return $this->config['lifetime_in_days'] * 60 * 60 * 24;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/sample-website/config.php.dist:
--------------------------------------------------------------------------------
1 | 'http://flarum.example.com',
5 | // Domain of your main site (without http://)
6 | 'root_domain' => 'example.com',
7 | // Create a random key in the api_keys table of your Flarum forum
8 | 'flarum_api_key' => 'NotSecureToken',
9 | // Random token to create passwords
10 | 'password_token' => 'NotSecureToken',
11 | // How many days should the login be valid
12 | 'lifetime_in_days' => 14,
13 | ];
14 |
--------------------------------------------------------------------------------
/sample-website/index.php:
--------------------------------------------------------------------------------
1 | [
7 | 'password' => 'password',
8 | 'email' => 'user@example.com',
9 | ],
10 | 'admin' => [
11 | 'password' => 'password',
12 | 'email' => 'user1@example.com',
13 | ],
14 | ];
15 |
16 | $username = empty($_POST['username']) ? '' : $_POST['username'];
17 | $password = empty($_POST['password']) ? '' : $_POST['password'];
18 |
19 | if (isset($users[$username]) && $users[$username]['password'] === $password) {
20 | $email = $users[$username]['email'];
21 | $forum = new Forum();
22 | $forum->login($username, $email);
23 | $forum->redirectToForum();
24 | } elseif (!empty($username) || !empty($password)) {
25 | echo 'Login failed';
26 | }
27 | ?>
28 |
29 | Login
30 |
31 | = array_keys($users)[0] ?> / = $users['user']['password'] ?>
32 | = array_keys($users)[1] ?> / = $users['admin']['password'] ?>
33 |
34 |
39 |
--------------------------------------------------------------------------------
/sample-website/logout.php:
--------------------------------------------------------------------------------
1 | logout();
8 |
9 | if ($_GET['forum']) {
10 | $forum->redirectToForum();
11 | }
12 |
--------------------------------------------------------------------------------
/sample-website/wordpress-flarum-sso.php:
--------------------------------------------------------------------------------
1 | redirectToForum();
16 | }
17 |
18 | return $redirect_to;
19 | }
20 |
21 | add_filter('login_redirect', 'flarum_sso_login_redirect', 10, 3);
22 |
23 | function flarum_sso_login($user_login, $user)
24 | {
25 | global $flarum;
26 |
27 | $flarum->login($user->user_login, $user->user_email);
28 | }
29 |
30 | add_action('wp_login', 'flarum_sso_login', 10, 2);
31 |
32 | function flarum_sso_logout()
33 | {
34 | global $flarum;
35 |
36 | $flarum->logout();
37 | }
38 |
39 | add_action('wp_logout', 'flarum_sso_logout');
40 |
--------------------------------------------------------------------------------
/src/Listener/ActivateUser.php:
--------------------------------------------------------------------------------
1 | listen(Registered::class, [$this, 'activateUser']);
13 | }
14 |
15 | public function activateUser(Registered $event)
16 | {
17 | $user = $event->user;
18 | $user->activate();
19 | $user->save();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Listener/AddClientAssets.php:
--------------------------------------------------------------------------------
1 | listen(ConfigureWebApp::class, [$this, 'addAssets']);
15 | $events->listen(ConfigureLocales::class, [$this, 'configureLocales']);
16 | }
17 |
18 | public function addAssets(ConfigureWebApp $event)
19 | {
20 | if ($event->isForum()) {
21 | $event->addAssets([
22 | __DIR__ . '/../../js/forum/dist/extension.js',
23 | ]);
24 | $event->addBootstrapper('wuethrich44/sso/main');
25 | }
26 |
27 |
28 | if ($event->isAdmin()) {
29 | $event->addAssets([
30 | __DIR__ . '/../../js/admin/dist/extension.js'
31 | ]);
32 | $event->addBootstrapper('wuethrich44/sso/main');
33 | }
34 | }
35 |
36 | public function configureLocales(ConfigureLocales $event)
37 | {
38 | foreach (new DirectoryIterator(__DIR__ . '/../../locale') as $file) {
39 | if ($file->isFile() && in_array($file->getExtension(), ['yml', 'yaml'])) {
40 | $event->locales->addTranslations($file->getBasename('.' . $file->getExtension()), $file->getPathname());
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Listener/AddLogoutRedirect.php:
--------------------------------------------------------------------------------
1 | settings = $settings;
16 | }
17 |
18 | public function subscribe(Dispatcher $events)
19 | {
20 | $events->listen(LoggedOut::class, [$this, 'addLogoutRedirect']);
21 | }
22 |
23 | public function addLogoutRedirect()
24 | {
25 | $url = $this->settings->get('wuethrich44-sso.logout_url');
26 |
27 | header('Location: ' . $url);
28 | die();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Listener/LoadSettingsFromDatabase.php:
--------------------------------------------------------------------------------
1 | settings = $settings;
18 | }
19 |
20 | public function subscribe(Dispatcher $events)
21 | {
22 | $events->listen(Serializing::class, [$this, 'prepareApiAttributes']);
23 | }
24 |
25 | public function prepareApiAttributes(Serializing $event)
26 | {
27 | if ($event->isSerializer(ForumSerializer::class)) {
28 | $event->attributes['wuethrich44-sso.signup_url'] = $this->settings->get('wuethrich44-sso.signup_url');
29 | $event->attributes['wuethrich44-sso.login_url'] = $this->settings->get('wuethrich44-sso.login_url');
30 | $event->attributes['wuethrich44-sso.logout_url'] = $this->settings->get('wuethrich44-sso.logout_url');
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------