├── .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 |

/

32 |

/

33 | 34 |
35 | 36 | 37 | 38 |
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 | --------------------------------------------------------------------------------