self-auth-rails 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +68 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/config/self-auth-rails/app.js +89 -0
  6. data/app/assets/config/self-auth-rails/cable.js +14 -0
  7. data/app/assets/config/self-auth-rails/channels/auth.js +7 -0
  8. data/app/assets/config/self-auth-rails/channels/messages_channel.js +150 -0
  9. data/app/assets/config/self-auth-rails/cookies.js +2 -0
  10. data/app/assets/config/self-auth-rails/ki.js +8 -0
  11. data/app/assets/config/self-auth-rails/manifest.js +8 -0
  12. data/app/assets/stylesheets/self-auth-rails/application.css +27 -0
  13. data/app/channels/application_cable/channel.rb +4 -0
  14. data/app/channels/application_cable/connection.rb +15 -0
  15. data/app/channels/messages_channel.rb +9 -0
  16. data/app/controllers/self_auth_rails/application_controller.rb +4 -0
  17. data/app/controllers/self_auth_rails/sessions_controller.rb +90 -0
  18. data/app/helpers/self_auth_rails/application_helper.rb +24 -0
  19. data/app/helpers/self_auth_rails/sessions_helper.rb +4 -0
  20. data/app/jobs/self_auth_rails/application_job.rb +4 -0
  21. data/app/mailers/self_auth_rails/application_mailer.rb +6 -0
  22. data/app/models/self_auth_rails/application_record.rb +5 -0
  23. data/app/services/self_auth_response_manager_service.rb +76 -0
  24. data/app/views/self_auth_rails/sessions/create.html.erb +2 -0
  25. data/app/views/self_auth_rails/sessions/new.html.erb +75 -0
  26. data/config/routes.rb +11 -0
  27. data/lib/self-auth-rails/engine.rb +18 -0
  28. data/lib/self-auth-rails/version.rb +3 -0
  29. data/lib/self-auth-rails.rb +50 -0
  30. data/lib/tasks/initializer.rb.tpl +45 -0
  31. data/lib/tasks/self_auth_rails_tasks.rake +69 -0
  32. metadata +116 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f7bf53acb259dc5c315fcf6222d30d099a3c95ada6b9bacb5d51554d8dc6ec08
4
+ data.tar.gz: 7d0fb909e65049f4f3ae8a16ccb15d0f82510a8d1f03bab09b84b4187aad37b2
5
+ SHA512:
6
+ metadata.gz: 51c1afac76389b629e41e293dd2e1d21826e3629602b3f7a770073a1731168cec1ccec7a66cdcf1a5c61c8d87b8772b9ee28302b6b3296cd92ad2fbe2c1ed477
7
+ data.tar.gz: 10e87468d332a8106727f5ecb2c75eab382163df0fbe5468d91dbc11e3a69fcb2fd4e7befdd01a089c8dae1024ef3183b79ceb1a1e295f457ee075e8dde0e61c
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2022 Self Group Ltd.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # self-auth-rails
2
+
3
+ self-auth-rails is rails engine to easily integrate [Self Authentication](https://docs.joinself.com/authentication/) on your authentication workflow.
4
+
5
+ ## Usage
6
+ How to use my plugin.
7
+
8
+ ## Getting started
9
+ ### Installation
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem "self-auth-rails"
14
+ ```
15
+
16
+ And then execute:
17
+ ```bash
18
+ $ bundle install
19
+ ```
20
+
21
+ ### Set up
22
+
23
+ For your convenience `self-auth-rails` comes with a script to automatically set up the initializers and mounting routes on your system.
24
+
25
+ ```
26
+ $ rake self_auth_rails:init
27
+ ```
28
+
29
+ This command will
30
+ - [x] Generate necessary migrations
31
+ - [x] Create initializers (config/initializers/self_auth_rails.rb)
32
+ - [x] Mount the engine on your routes (config/routes.rb)
33
+
34
+ Once that's done you'll need to:
35
+
36
+ - [ ] run the created migrations with `bin/rails db:migrate`
37
+ - [ ] setup the environment variables required on `config/initializers/self_auth_rails.rb`
38
+ - [ ] protect the resources you want to hide from the public
39
+
40
+ ### Protecting routes
41
+
42
+ You can fully protect your app by adding a controller filter to your `app/controllers/application.rb`, or specifying it only for the routes you want to be protected.
43
+ ```
44
+ class ApplicationController < ActionController::Base
45
+ before_action :authenticate_user!
46
+ end
47
+ ```
48
+
49
+ ### Running it
50
+
51
+ That's it, as soon as you have the SELF required environment variables in place, you'll be able to see the authentication screen when you visit http://localhost:3000.
52
+
53
+
54
+ ### Notes
55
+ #### Action cable
56
+ By default action cable relies on redis to work on development, if you're just testing this gem, you can switch it to `async` on `config/cable.yml`
57
+ ```
58
+ development:
59
+ adapter: async
60
+
61
+ #...
62
+ ```
63
+
64
+ ## Contributing
65
+ Bug reports and pull requests are welcome on GitHub at https://github.com/joinself/self-auth-rails.
66
+
67
+ ## License
68
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
@@ -0,0 +1,89 @@
1
+ /* Initialize app when page loads */
2
+ $(function(){
3
+ var Messages = App.cable.subscriptions.create({
4
+ channel:'MessagesChannel',
5
+ conversation_id: connectionID()
6
+ }, {
7
+ received: function(data) {
8
+ console.log(data);
9
+ if(data.type == "info") {
10
+ if(data.status == "completed") {
11
+ console.log("info request");
12
+ window.location.replace("/");
13
+ } else {
14
+ message = "Fact request has been rejected"
15
+ if(data.status == "errored") {
16
+ message = data.message
17
+ }
18
+ // TODO: this id no longer exists...
19
+ $('#info_messages').append("<div class='alert alert-danger'><button type='button' class='close' data-dismiss='alert' aria-hidden='true'>&times;</button>'+message+'</div>");
20
+ }
21
+ } else {
22
+ $("#messages").removeClass('hidden');
23
+ if(data.status == "accepted") {
24
+ // Just display the
25
+ $('#process').removeClass("hidden");
26
+ $('#non-dl-container').addClass("hidden");
27
+ $('#dl-container').addClass("hidden");
28
+
29
+ // Login request accepted
30
+ $("#req-auth .activity-icon").addClass("bg-success")
31
+ $("#req-auth .activity-icon").addClass("shadow-success")
32
+ $("#req-auth .activity-icon").removeClass("bg-info")
33
+ $("#req-auth .activity-icon").removeClass("shadow-info")
34
+
35
+ $("#req-info .activity-icon").removeClass("bg-secondary")
36
+ $("#req-info .activity-icon").removeClass("shadow-secondary")
37
+ $("#req-info .activity-icon").addClass("bg-info")
38
+ $("#req-info .activity-icon").addClass("shadow-info")
39
+
40
+ $("#req-auth .waiting").addClass("hidden")
41
+ $("#req-auth .passed").removeClass("hidden")
42
+ $("#req-info .waiting").removeClass("hidden")
43
+
44
+ } else if(data.status == "completed") {
45
+ $("#auth-ok").hide()
46
+
47
+ $("#req-info .activity-icon").addClass("bg-success")
48
+ $("#req-info .activity-icon").addClass("shadow-success")
49
+ $("#req-info .activity-icon").removeClass("bg-info")
50
+ $("#req-info .activity-icon").removeClass("shadow-info")
51
+
52
+ $("#req-info .waiting").addClass("hidden")
53
+ $("#req-info .passed").removeClass("hidden")
54
+ $(".logging-in").removeClass("hidden")
55
+ $(".btn-cancel").addClass("hidden")
56
+
57
+ setTimeout(() => {
58
+ // Fetched user information
59
+ $("#login-token")[0].value = data.token;
60
+ $("#form-login").off("submit")
61
+ $("#form-login")[0].submit();
62
+ }, 2000);
63
+
64
+ } else {
65
+
66
+ message = "Login request has been rejected"
67
+ if(data.status == "errored") {
68
+ message = data.message
69
+ }
70
+ $('#messages').append(`<div class="alert alert-warning alert-dismissible show fade"><div class="alert-body"><button class="close" data-dismiss="alert"><span>×</span></button>${message}</div></div>`);
71
+
72
+ $('#messages').show();
73
+ $('#process').addClass("hidden");
74
+ $('#non-dl-container').removeClass("hidden");
75
+
76
+ $("#req-auth .activity-icon").addClass("bg-info")
77
+ $("#req-auth .activity-icon").addClass("shadow-info")
78
+ $("#req-auth .activity-icon").removeClass("bg-success")
79
+ $("#req-auth .activity-icon").removeClass("shadow-success")
80
+
81
+ $("#req-info .activity-icon").removeClass("bg-info")
82
+ $("#req-info .activity-icon").removeClass("shadow-info")
83
+ $("#req-info .activity-icon").addClass("bg-secondary")
84
+ $("#req-info .activity-icon").addClass("shadow-secondary")
85
+ }
86
+ }
87
+ }
88
+ });
89
+ });
@@ -0,0 +1,14 @@
1
+ // Action Cable provides the framework to deal with WebSockets in Rails.
2
+ // You can generate new channels where WebSocket features live using the `rails generate channel` command.
3
+ //
4
+ //= require actioncable
5
+ //= require_self
6
+ //= require_tree ./channels
7
+
8
+ (function() {
9
+ this.App || (this.App = {});
10
+
11
+ App.cable = ActionCable.createConsumer();
12
+
13
+ }).call(this);
14
+
@@ -0,0 +1,7 @@
1
+ //= require self-auth-rails/cable
2
+ //= require_self
3
+ //= require_tree .
4
+
5
+ this.App = {};
6
+
7
+ App.cable = ActionCable.createConsumer();
@@ -0,0 +1,150 @@
1
+ function uuidv4() {
2
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
3
+ var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
4
+ return v.toString(16);
5
+ });
6
+ }
7
+
8
+ function connectionID() {
9
+ c = Cookies.get("auth-sess")
10
+ if(c == undefined) {
11
+ Cookies.set('auth-sess', uuidv4());
12
+ }
13
+ return Cookies.get("auth-sess");
14
+ }
15
+
16
+ function displayQR() {
17
+ updateQR();
18
+ $('#qr-container').show();
19
+ $('#page-wrapper').addClass('page-loading');
20
+ window.qrInterval = window.setInterval(function(){
21
+ if($('#qr').length) {
22
+ updateQR();
23
+ }
24
+ }, 30000);
25
+ }
26
+
27
+ function hideQR() {
28
+ location.reload();
29
+ }
30
+
31
+ function updateQR() {
32
+ if($("#qr_url").length) {
33
+ $("#qr")[0].setAttribute("src", $("#qr_url")[0].value + "?uuid=" + connectionID());
34
+ }
35
+ }
36
+
37
+ function updateDL() {
38
+ if($("#dl_url").length) {
39
+ if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(navigator.userAgent)
40
+ || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(navigator.userAgent.substr(0, 4))) {
41
+ $("#dl-container").removeClass("hidden");
42
+ $("#non-dl-container").addClass("hidden");
43
+
44
+ $.getJSON($("#dl_url")[0].value + "?uuid=" + connectionID(), function (data) {
45
+ $("#dl").attr("href", data["url"]);
46
+
47
+ $('#dl').unbind('click');
48
+ $('#dl').bind("click", function (e) {
49
+ $('#page-wrapper').addClass('page-loading');
50
+ if($("#profile_request").length) {
51
+ hasProfile();
52
+ }
53
+ });
54
+ });
55
+ }
56
+ }
57
+ }
58
+
59
+ function hasProfile() {
60
+ $.ajax({
61
+ type: "GET",
62
+ contentType: "application/json",
63
+ url: "/session/has_profile",
64
+ dataType: 'json',
65
+ timeout: 600000,
66
+ success: function (data) {
67
+ window.location.replace("/");
68
+ },
69
+ error: function (e) {
70
+ setTimeout(function(){ hasProfile(); }, 2000);
71
+ }
72
+ });
73
+ }
74
+
75
+ ready = function() {
76
+
77
+ if($("#qr-container")){
78
+ displayQR();
79
+ }
80
+ if($("#dl-container")) {
81
+ updateDL();
82
+ }
83
+
84
+ $("#switch-not-dl").bind("click", function(e){
85
+ $("#dl-container").addClass("hidden");
86
+ $("#non-dl-container").removeClass("hidden");
87
+ $("#selfid-field").focus();
88
+ $("#qr").width($(".qr-wrapper").width());
89
+ $("#qr").height($(".qr-wrapper").width());
90
+ });
91
+
92
+ $("#profile_request").bind("click", function (e) {
93
+ e.preventDefault(e);
94
+ $("#profile_help_block").show();
95
+ console.log("sending ajax request")
96
+ $.ajax({
97
+ type: "POST",
98
+ contentType: "application/json",
99
+ url: "/profile/fetch",
100
+ dataType: 'json',
101
+ data: '{"user":{"connection_id":"'+connectionID()+'"}}',
102
+ beforeSend: $.rails.CSRFProtection,
103
+ timeout: 600000,
104
+ success: function (data) {
105
+ },
106
+ error: function (e) {
107
+ if(e.status == 502) {
108
+ $('#info_messages').append("<div class='alert alert-danger'><button type='button' class='close' data-dismiss='alert' aria-hidden='true'>&times;</button>Request timed out, try again later</div>");
109
+ } else {
110
+ $('#info_messages').append("<div class='alert alert-danger'><button type='button' class='close' data-dismiss='alert' aria-hidden='true'>&times;</button>"+e.responseJSON.error+"</div>");
111
+ }
112
+ $("#profile_help_block").hide();
113
+ }
114
+ });
115
+
116
+ });
117
+
118
+ $('#form-login').bind("submit", function(e){
119
+ e.preventDefault();
120
+ var val = $('#selfid-field')[0].value
121
+ if(val == null) {
122
+ return;
123
+ }
124
+ if (val.length != 11) {
125
+ $('#selfid-field').addClass("is-invalid")
126
+ return;
127
+ }
128
+ $('#selfid-field').removeClass("is-invalid")
129
+ $('#page-wrapper').addClass('page-loading');
130
+ $(this).blur();
131
+
132
+ //do some verification
133
+ $("#connection_id")[0].value = connectionID();
134
+ $("#process").removeClass("hidden");
135
+ $('#non-dl-container').addClass("hidden");
136
+
137
+ $.ajax({
138
+ type: 'POST',
139
+ url: $(this).context.action,
140
+ data: $(this).serialize()
141
+ });
142
+ });
143
+
144
+ };
145
+
146
+ $(function () {
147
+ updateQR()
148
+ })
149
+
150
+
@@ -0,0 +1,2 @@
1
+ /*! js-cookie v2.1.4 | MIT */
2
+ !function(a){var b=!1;if("function"==typeof define&&define.amd&&(define(a),b=!0),"object"==typeof exports&&(module.exports=a(),b=!0),!b){var c=window.Cookies,d=window.Cookies=a();d.noConflict=function(){return window.Cookies=c,d}}}(function(){function a(){for(var a=0,b={};a<arguments.length;a++){var c=arguments[a];for(var d in c)b[d]=c[d]}return b}function b(c){function d(b,e,f){var g;if("undefined"!=typeof document){if(arguments.length>1){if(f=a({path:"/"},d.defaults,f),"number"==typeof f.expires){var h=new Date;h.setMilliseconds(h.getMilliseconds()+864e5*f.expires),f.expires=h}f.expires=f.expires?f.expires.toUTCString():"";try{g=JSON.stringify(e),/^[\{\[]/.test(g)&&(e=g)}catch(p){}e=c.write?c.write(e,b):encodeURIComponent(e+"").replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g,decodeURIComponent),b=encodeURIComponent(b+""),b=b.replace(/%(23|24|26|2B|5E|60|7C)/g,decodeURIComponent),b=b.replace(/[\(\)]/g,escape);var i="";for(var j in f)f[j]&&(i+="; "+j,!0!==f[j]&&(i+="="+f[j]));return document.cookie=b+"="+e+i}b||(g={});for(var k=document.cookie?document.cookie.split("; "):[],l=0;l<k.length;l++){var m=k[l].split("="),n=m.slice(1).join("=");'"'===n.charAt(0)&&(n=n.slice(1,-1));try{var o=m[0].replace(/(%[0-9A-Z]{2})+/g,decodeURIComponent);if(n=c.read?c.read(n,o):c(n,o)||n.replace(/(%[0-9A-Z]{2})+/g,decodeURIComponent),this.json)try{n=JSON.parse(n)}catch(p){}if(b===o){g=n;break}b||(g[o]=n)}catch(p){}}return g}}return d.set=d,d.get=function(a){return d.call(d,a)},d.getJSON=function(){return d.apply({json:!0},[].slice.call(arguments))},d.defaults={},d.remove=function(b,c){d(b,"",a(c,{expires:-1}))},d.withConverter=b,d}return b(function(){})});
@@ -0,0 +1,8 @@
1
+ /*!
2
+ * ki.js v1.1.0 - 2015-10-06
3
+ * Copyright (c) 2015 Denis Ciccale (@tdecs)
4
+ * Released under MIT license
5
+ */
6
+ !function(a,b,c,d){function e(c){b.push.apply(this,c&&c.nodeType?[c]:""+c===c?a.querySelectorAll(c):d)}$=function(b){return/^f/.test(typeof b)?/c/.test(a.readyState)?b():$(a).on("DOMContentLoaded",b):new e(b)},$[c]=e[c]=$.fn=e.fn={length:0,on:function(a,b){return this.each(function(c){c.addEventListener(a,b)})},off:function(a,b){return this.each(function(c){c.removeEventListener(a,b)})},each:function(a,c){return b.forEach.call(this,a,c),this},splice:b.splice}}(document,[],"prototype");
7
+
8
+ !function(){$.each=function(a,b){for(var c=0,d=a.length;d>c;++c)b.call(a[c],c,a[c]);return this};var a=["addClass","removeClass","toggleClass"],b=["add","remove","toggle"];a.forEach(function(a,c){$.prototype[a]=function(a){return this.each(function(d){d.classList[b[c]](a)})}}),$.prototype.hasClass=function(a){return this[0].classList.contains(a)},$.prototype.append=function(a){return this.each(function(b){b.appendChild(a[0])})},$.prototype.prepend=function(a){return this.each(function(b){b.insertBefore(a[0],b.firstChild)})},$.prototype.hide=function(){return this.each(function(a){a.style.display="none"})},$.prototype.show=function(){return this.each(function(a){a.style.display=""})},$.prototype.attr=function(a,b){return b===[]._?this[0].getAttribute(a):this.each(function(c){c.setAttribute(a,b)})},$.prototype.removeAttr=function(a){return this.each(function(b){b.removeAttribute(a)})},$.prototype.hasAttr=function(a){return this[0].hasAttribute(a)},$.prototype.before=function(a){return this.each(function(b){b.insertAdjacentHTML("beforebegin",a)})},$.prototype.after=function(a){return this.each(function(b){b.insertAdjacentHTML("afterend",a)})},$.prototype.css=function(a,b){if("object"==typeof a){for(var c in a)this.each(function(b){b.style[c]=a[c]});return this}return b===[]._?this[0].style[a]:this.each(function(c){c.style[a]=b})},$.prototype.first=function(){return $(this[0])},$.prototype.last=function(){return $(this[this.length-1])},$.prototype.get=function(a){return $(this[a])},$.prototype.text=function(a){return a===[]._?this[0].textContent:this.each(function(b){b.textContent=a})},$.prototype.html=function(a){return a===[]._?this[0].innerHTML:this.each(function(b){b.innerHTML=a})},$.prototype.parent=function(){return this.length<2?$(this[0].parentNode):[]},$.prototype.remove=function(){return this.each(function(a){a.parentNode.removeChild(a)})},$.trim=function(a){return a.replace(/^\s+|\s+$/g,"")},$.prototype.trigger=function(a){if(document.createEvent){var b=document.createEvent("HTMLEvents");b.initEvent(a,!0,!1),this.each(function(a){a.dispatchEvent(b)})}else this.each(function(b){b.fireEvent("on"+a)})},$.prototype.is=function(a){var b=this[0].matches||this[0].matchesSelector||this[0].msMatchesSelector||this[0].mozMatchesSelector||this[0].webkitMatchesSelector||this[0].oMatchesSelector;if(b)return b.call(this[0],a);for(var c=this[0].parentNode.querySelectorAll(a),d=c.length;d--;)if(c[d]===this[0])return!0;return!1},"filter map".split(" ").forEach(function(a){$[a]=function(b,c){return b[a](c)}}),$.stop=function(a){a.preventDefault?a.preventDefault():a.returnValue=!1},$.param=function(a,b){var c=[];for(var d in a){var e=b?b+"["+d+"]":d,f=a[d];c.push("object"==typeof f?$.param(f,e):encodeURIComponent(e)+"="+encodeURIComponent(f))}return c.join("&")},$.ajax=function(a,b,c){var d=new XMLHttpRequest,e=new $.Deferred,f="object"==typeof b?1:0,g=["GET","POST"];d.open(g[f],a,!0);var h=f?c:b;return"undefined"==typeof c&&"function"!=typeof b&&(h=function(){}),d.onerror=function(){e.reject(this),h(this,!0)},d.onreadystatechange=function(){4===this.readyState&&(this.status>=200&&this.status<400?(e.resolve(this.response),h(this.response,!0)):(e.reject(this),h(this,!0)))},f?(d.setRequestHeader("Content-type","application/x-www-form-urlencoded"),d.send($.param(b))):d.send(),d=null,e.promise()},function(a){function b(a){return"[object Array]"===Object.prototype.toString.call(a)}function c(a,c){if(b(a))for(var d=0;d<a.length;d++)c(a[d]);else c(a)}function d(a){var e="pending",f=[],g=[],h=[],i=[],j={done:function(){for(var a=0;a<arguments.length;a++)if(arguments[a])if(b(arguments[a]))for(var c=arguments[a],d=0;d<c.length;d++)"resolved"===e&&c[d].apply(this,i),f.push(c[d]);else"resolved"===e&&arguments[a].apply(this,i),f.push(arguments[a]);return this},fail:function(){for(var a=0;a<arguments.length;a++)if(arguments[a])if(b(arguments[a]))for(var c=arguments[a],d=0;d<c.length;d++)"rejected"===e&&c[d].apply(this,i),g.push(c[d]);else"rejected"===e&&arguments[a].apply(this,i),g.push(arguments[a]);return this},always:function(){return this.done.apply(this,arguments).fail.apply(this,arguments)},progress:function(){for(var a=0;a<arguments.length;a++)if(arguments[a])if(b(arguments[a]))for(var c=arguments[a],d=0;d<c.length;d++)"pending"===e&&h.push(c[d]);else"pending"===e&&h.push(arguments[a]);return this},then:function(){arguments.length>1&&arguments[1]&&this.fail(arguments[1]),arguments.length>0&&arguments[0]&&this.done(arguments[0]),arguments.length>2&&arguments[2]&&this.progress(arguments[2])},promise:function(a){if("undefined"==typeof a)return j;for(var b in j)a[b]=j[b];return a},state:function(){return e},debug:function(){console.log("[debug]",f,g,e)},isRejected:function(){return"rejected"===e},isResolved:function(){return"resolved"===e},pipe:function(a,b,e){return d(function(d){c(a,function(a){"function"==typeof a?k.done(function(){var b=a.apply(this,arguments);b&&"function"==typeof b?b.promise().then(d.resolve,d.reject,d.notify):d.resolve(b)}):k.done(d.resolve)}),c(b,function(a){"function"==typeof a?k.fail(function(){var b=a.apply(this,arguments);b&&"function"==typeof b?b.promise().then(d.resolve,d.reject,d.notify):d.reject(b)}):k.fail(d.reject)})}).promise()}},k={resolveWith:function(a){if("pending"===e){e="resolved";for(var b=i=arguments.length>1?arguments[1]:[],c=0;c<f.length;c++)f[c].apply(a,b)}return this},rejectWith:function(a){if("pending"===e){e="rejected";for(var b=i=arguments.length>1?arguments[1]:[],c=0;c<g.length;c++)g[c].apply(a,b)}return this},notifyWith:function(a){if("pending"===e)for(var b=i=arguments.length>1?arguments[1]:[],c=0;c<h.length;c++)h[c].apply(a,b);return this},resolve:function(){return this.resolveWith(this,arguments)},reject:function(){return this.rejectWith(this,arguments)},notify:function(){return this.notifyWith(this,arguments)}},l=j.promise(k);return a&&a.apply(l,[l]),l}var e=function(){if(arguments.length<2){var a=arguments.length?arguments[0]:void 0;return a&&"function"==typeof a.isResolved&&"function"==typeof a.isRejected?a.promise():d().resolve(a).promise()}return function(a){for(var b=d(),c=a.length,e=0,f=new Array(c),g=0;g<a.length;g++)!function(d){var g=null;a[d].done?a[d].done(function(){f[d]=arguments.length<2?arguments[0]:arguments,++e==c&&b.resolve.apply(b,f)}).fail(function(){b.reject(arguments)}):(g=a[d],a[d]=new Deferred,a[d].done(function(){f[d]=arguments.length<2?arguments[0]:arguments,++e==c&&b.resolve.apply(b,f)}).fail(function(){b.reject(arguments)}).resolve(g))}(g);return b.promise()}(arguments)};a.Deferred=d,a.when=d.when=e}($)}();
@@ -0,0 +1,8 @@
1
+ //= link self-auth-rails/application.css
2
+
3
+ //= require ./ki.js
4
+ //= require ./cookies.js
5
+ //= require ./cable.js
6
+ //= require ./app.js
7
+ //= require_tree .
8
+
@@ -0,0 +1,27 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
16
+
17
+ #auth-ok {
18
+ position: absolute;
19
+ padding-left: 190px;
20
+ padding-top: 110px;
21
+ font-size: 100px;
22
+ display: none;
23
+ }
24
+
25
+ div.hidden {
26
+ display: none;
27
+ }
@@ -0,0 +1,4 @@
1
+ module ApplicationCable
2
+ class Channel < ActionCable::Channel::Base
3
+ end
4
+ end
@@ -0,0 +1,15 @@
1
+ module ApplicationCable
2
+ class Connection < ActionCable::Connection::Base
3
+ identified_by :current_user
4
+
5
+ def connect
6
+ self.current_user = find_verified_user
7
+ end
8
+
9
+ protected
10
+
11
+ def find_verified_user
12
+ SecureRandom.urlsafe_base64
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ class MessagesChannel < ApplicationCable::Channel
2
+ def subscribed
3
+ stream_from "conversation_#{params['conversation_id']}_channel"
4
+ end
5
+
6
+ def unsubscribed
7
+ # Any cleanup needed when channel is unsubscribed
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ module SelfAuthRails
2
+ class ApplicationController < ::ApplicationController
3
+ end
4
+ end
@@ -0,0 +1,90 @@
1
+ module SelfAuthRails
2
+ class SessionsController < ::ApplicationController
3
+ def new; end
4
+
5
+ # Authenticates a user with the given token
6
+ def create
7
+ if session_params[:token].empty? # AJAX request
8
+ SelfAuthRails.self_client.facts.request(session_params[:selfid],
9
+ [:display_name],
10
+ auth: true,
11
+ cid: session_params[:connection_id],
12
+ async: true)
13
+
14
+ request.format = :json
15
+ respond_to do |format|
16
+ format.json { head :no_content }
17
+ end
18
+ else # Form submission
19
+ reset_user_token(session_params[:token])
20
+ respond_to do |format|
21
+ format.html { redirect_to SelfAuthRails.authenticated_path, notice: 'Welcome.' }
22
+ end
23
+ end
24
+ end
25
+
26
+ # Logs out the current user.
27
+ def logout
28
+ session[:user_id] = nil
29
+
30
+ respond_to do |format|
31
+ format.html { redirect_to new_url }
32
+ format.json { head :no_content }
33
+ end
34
+ end
35
+
36
+ # Generates a QR code for authenticating users.
37
+ def qr
38
+ if Rails.env.test?
39
+ send_data("test")
40
+ else
41
+ uuid = "qr::#{params[:uuid]}"
42
+
43
+ img = ::SelfClient.authentication.generate_qr(
44
+ facts: SelfAuthRails.auth_facts,
45
+ cid: uuid,
46
+ exp_timeout: 86_400
47
+ )
48
+
49
+ send_data(img.as_png(border: 0, size: 400),
50
+ type: 'image/png',
51
+ disposition: 'inline')
52
+ end
53
+ end
54
+
55
+ # Generates a dynamic link to authenticate users.
56
+ def dl
57
+ link = '#'
58
+ unless Rails.env.test?
59
+ uuid = "dl::#{params[:uuid]}"
60
+ link = SelfAuthRails.self_client.facts.generate_deep_link(SelfAuthRails.auth_facts,
61
+ SelfAuthRails.authenticated_path,
62
+ cid: uuid,
63
+ auth: true)
64
+ end
65
+ render json: { url: link }
66
+ end
67
+
68
+ protected
69
+
70
+ # Never trust parameters from the scary internet, only allow the white list through.
71
+ def session_params
72
+ params.permit(:selfid, :token, :connection_id, :qr_url, :dl_url, :authenticity_token)
73
+ end
74
+
75
+ def auto_join_authenticated_users
76
+ redirect_to SelfAuthRails.authenticated_path unless session[:user_id].nil?
77
+ end
78
+
79
+ def reset_user_token(token)
80
+ @user = SelfAuthRails.session_class.find_by(token: token)
81
+ if @user.nil? # The user is already created on a different tab
82
+ @user = helpers.current_user
83
+ else
84
+ @user.token = ''
85
+ @user.save!
86
+ session[:user_id] = @user.id.to_s
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,24 @@
1
+ module SelfAuthRails
2
+ module ApplicationHelper
3
+ def authenticate_user!
4
+ current_url = request.base_url + request.path
5
+
6
+ uri = URI(main_app.root_url)
7
+
8
+ new_url = SelfAuthRails::Engine.routes.url_helpers.new_url(host: uri.host, port: uri.port)
9
+ create_url = SelfAuthRails::Engine.routes.url_helpers.create_url(host: uri.host, port: uri.port)
10
+ qr_url = SelfAuthRails::Engine.routes.url_helpers.qr_url(host: uri.host, port: uri.port)
11
+ dl_url = SelfAuthRails::Engine.routes.url_helpers.dl_url(host: uri.host, port: uri.port)
12
+
13
+ if [new_url, create_url, qr_url, dl_url].include? current_url
14
+ redirect_to root_url unless current_user.nil?
15
+ else
16
+ redirect_to new_url if current_user.nil?
17
+ end
18
+ end
19
+
20
+ def current_user
21
+ SelfAuthRails.session_class.find_by(id: session[:user_id])
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,4 @@
1
+ module SelfAuthRails
2
+ module SessionsHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module SelfAuthRails
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module SelfAuthRails
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module SelfAuthRails
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,76 @@
1
+ # Manages self authentication responses
2
+ class SelfAuthResponseManagerService
3
+ STATUS_ERR = 'errored'.freeze
4
+ STATUS_REJ = 'rejected'.freeze
5
+ STATUS_OK = 'accepted'.freeze
6
+ STATUS_COM = 'completed'.freeze
7
+ STATUS_RECIEVED = 'received'.freeze
8
+
9
+ def initialize(broadcaster)
10
+ @broadcaster = broadcaster
11
+ end
12
+
13
+ # Processes a Self authentication response
14
+ def process_response(self_auth_response)
15
+ return if self_auth_response.nil?
16
+
17
+ cid = self_auth_response.id.split('_').first.split('::').last
18
+ channel = "conversation_#{cid}_channel"
19
+
20
+ if self_auth_response.errored?
21
+ broadcast_status_change channel, STATUS_ERR, message: 'A timeout ocured while waiting for your device response'
22
+ return
23
+ end
24
+
25
+ if self_auth_response.rejected?
26
+ broadcast_status_change channel, STATUS_REJ
27
+ return
28
+ end
29
+
30
+ unless admin?(self_auth_response.from)
31
+ broadcast_status_change channel, STATUS_ERR, message: 'You are forbidden to access this service'
32
+ return
33
+ end
34
+
35
+ user = set_user_token(self_auth_response.from, self_auth_response)
36
+ broadcast_status_change channel, STATUS_COM, token: user[:token]
37
+ end
38
+
39
+ private
40
+
41
+ # creates or updates a user setting a random token.
42
+ def set_user_token(selfid, self_auth_response)
43
+ user = {
44
+ token: SecureRandom.uuid,
45
+ selfid: selfid,
46
+ facts: {}
47
+ }
48
+
49
+ SelfAuthRails.auth_facts.each do |fact|
50
+ user[:facts][fact] = self_auth_response.attestation_values_for(fact).first
51
+ end
52
+
53
+ u = SelfAuthRails.session_class.find_by(selfid: user[:selfid])
54
+ u = SelfAuthRails.session_class.new(selfid: user[:selfid]) if u.nil?
55
+ u.token = user[:token]
56
+ SelfAuthRails.fact_mapping.each do |fact_name, field|
57
+ u.send("#{field}=", user[:facts][fact_name.to_sym])
58
+ end
59
+ u.save!
60
+
61
+ user
62
+ end
63
+
64
+ # broadcasts a status change through websockets to the web client
65
+ def broadcast_status_change(channel, status, opts = {})
66
+ token = opts.fetch(:token, '')
67
+ message = opts.fetch(:message, '')
68
+ type = opts.fetch(:type, 'auth')
69
+ @broadcaster.broadcast channel, { status: status, token: token, message: message, type: type }
70
+ end
71
+
72
+ # checks if the given id is in the admins list
73
+ def admin?(id)
74
+ ENV['ADMIN_IDS'].split(',').include? id
75
+ end
76
+ end
@@ -0,0 +1,2 @@
1
+ <h1>Sessions#create</h1>
2
+ <p>Find me in app/views/sessions/create.html.erb</p>
@@ -0,0 +1,75 @@
1
+ <%= hidden_field_tag "conversation_id", "", id: "conversation_id" %>
2
+ <%= form_with(model: @user, url: create_path, local: true, id: "form-login", class: "form-horizontal form-bordered form-control-borderless") do |form| %>
3
+ <%= form.hidden_field 'qr_url', id: 'qr_url', value: qr_path %>
4
+ <%= form.hidden_field 'dl_url', id: 'dl_url', value: dl_path %>
5
+ <%= form.hidden_field 'connection_id', id: "connection_id" %>
6
+ <%= form.hidden_field :token, id: "login-token" %>
7
+
8
+ <div id="app">
9
+ <section class="section">
10
+ <div class="container mt-5">
11
+ <div class="row">
12
+ <div class="col-12 col-sm-8 offset-sm-2 col-md-6 offset-md-3 col-lg-6 offset-lg-3 col-xl-6 offset-xl-3">
13
+ <div class="card card-primary">
14
+ <div class="card-body">
15
+
16
+ <div id="dl-container" class="hidden">
17
+ <div class="col-xs-12">
18
+ <h4>Welcome to Self</h4>
19
+ <p>Please click the button below to authenticate using your Self App</p>
20
+ </div>
21
+ <div class="text-center form-group" style="border:0px; background-color: #f4f4f4; padding-top:20px; padding-bottom:20px">
22
+ <a href="#" class='btn btn-success' id="dl">Authenticate with <strong>Self</strong></a>
23
+ <button id="switch-not-dl" class="btn btn-info"><i class="fa fa-qrcode"></i> </button>
24
+ </div>
25
+ </div>
26
+
27
+ <div id="non-dl-container">
28
+ <div id="qr-container" class='text-center form-group' style="border:0px;">
29
+ <div class="qr-wrapper" style='min-height:400px'>
30
+ <div id="auth-ok">
31
+ <i class="fa fa-check text-success"></i>
32
+ </div>
33
+
34
+ <img src="" id="qr" style="max-width:400px;max-height:400px;width: inherit;height: inherit;">
35
+ </div>
36
+ </div>
37
+
38
+ <div id='login-form-group' class="form-group" style="padding: 0 25px 0 25px;">
39
+ <div class="input-group mb-3">
40
+ <div class="input-group-prepend">
41
+ <div class="input-group-text">
42
+ <i class="fas fa-user"></i>
43
+ </div>
44
+ </div>
45
+ <%= form.text_field :selfid, placeholder: "Self identifier", class: "form-control input-lg", id: "selfid-field", required: true %>
46
+ <div class="invalid-feedback invalid-pad">Oh no! Self identifier does not exist.</div>
47
+ <div class="input-group-append">
48
+ <button id="sign-in" class="btn btn-primary"><i class="fa fa-angle-right"></i> Sign in</button>
49
+ </div>
50
+ </div>
51
+ </div>
52
+
53
+
54
+ <div class="form-group form-actions login-new-identifier">
55
+ <div class="row justify-content-md-center">
56
+ <div class="col">
57
+ <small>Don't have a Self Identifier?</small><br />
58
+ <a href="javascript:void(0)" id="link-register-login"><small class="text-info">Download the Self app and create a Self identifier</small></a>
59
+ </div>
60
+ <div class="col col-lg-4 text-right" style="padding-top:10px;">
61
+ <button class="btn btn-sm btn-info"><i class="fa fa-angle-right"></i> Get the app</button>
62
+ </div>
63
+ </div>
64
+
65
+ </div>
66
+ </div>
67
+
68
+ </div>
69
+ </div>
70
+ </div>
71
+ </div>
72
+ </div>
73
+ </section>
74
+ </div>
75
+ <% end %>
data/config/routes.rb ADDED
@@ -0,0 +1,11 @@
1
+ SelfAuthRails::Engine.routes.draw do
2
+ get 'new', to: 'sessions#new'
3
+ post 'create', to: 'sessions#create'
4
+ get 'qr', to: 'sessions#qr'
5
+ get 'dl', to: 'sessions#dl'
6
+ get 'logout', to: 'sessions#logout'
7
+
8
+ # Serve websocket cable requests in-process
9
+ mount ActionCable.server => '/auth'
10
+ end
11
+
@@ -0,0 +1,18 @@
1
+ module SelfAuthRails
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace SelfAuthRails
4
+
5
+ initializer "self-auth-rails.helpers" do
6
+ ActiveSupport.on_load(:action_controller_base) do
7
+ helper SelfAuthRails::Engine.helpers
8
+ include SelfAuthRails::Engine.helpers
9
+ end
10
+ end
11
+
12
+ initializer "self-auth-rails.assets.precompile" do |app|
13
+ app.config.assets.precompile << "manifest.js"
14
+ app.config.assets.precompile << "application.css"
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module SelfAuthRails
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,50 @@
1
+ require "self-auth-rails/version"
2
+ require "self-auth-rails/engine"
3
+
4
+ module SelfAuthRails
5
+ mattr_accessor :self_client,
6
+ :session_class_name,
7
+ :auth_facts,
8
+ :fact_mapping,
9
+ :authenticated_path,
10
+ :authenticated_path
11
+
12
+ class Engine < ::Rails::Engine
13
+ default_auth_facts = [:display_name]
14
+ default_authenticated_path = '/'
15
+
16
+ config.after_initialize do
17
+ unless SelfAuthRails::self_client.nil?
18
+ SelfAuthRails.auth_facts ||= default_auth_facts
19
+ SelfAuthRails.authenticated_path ||= default_authenticated_path
20
+
21
+ response_manager = SelfAuthResponseManagerService.new(ActionCable.server)
22
+
23
+ # Subscribe to fact responses
24
+ SelfAuthRails::self_client.facts.subscribe do |auth|
25
+ response_manager.process_response(auth)
26
+ end
27
+
28
+ # Subscribe to authentication responses
29
+ SelfAuthRails::self_client.authentication.subscribe do |auth|
30
+ response_manager.process_response(auth)
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ def self.session_class
37
+ class_name = session_class_name || 'User'
38
+ Object.const_get(class_name)
39
+ end
40
+
41
+ # this function maps the vars from your app into your engine
42
+ def self.setup(&block)
43
+ yield self
44
+ end
45
+
46
+ def authenticated_path
47
+ SelfAuthRails::authenticated_path || SelfAuthRails::default_authenticated_path
48
+ end
49
+
50
+ end
@@ -0,0 +1,45 @@
1
+ require 'selfsdk'
2
+
3
+ if defined?(Rails::Server) && !defined?(::SelfClient)
4
+ # If self client is already initialized skip this initialization
5
+ return unless defined?(::SelfClient).nil?
6
+
7
+ # Initializes self client
8
+ ::SelfClient = SelfSDK::App.new(
9
+ ENV['SELF_APP_ID'],
10
+ ENV['SELF_APP_KEY'],
11
+ ENV['SELF_STORAGE_KEY'],
12
+ ENV['SELF_STORAGE_DIR'],
13
+ env: ENV['SELF_ENVIRONMENT'],
14
+ )
15
+
16
+ # Setup self-auth-rails engine
17
+ SelfAuthRails.setup do |config|
18
+ # Initializes the self-ruby-sdk client.
19
+ config.self_client = ::SelfClient
20
+
21
+ # Sets rails logger as default self-ruby-sdk logger (optional).
22
+ SelfSDK.logger = Rails.logger
23
+
24
+ # Defines the facts we're going to require for a user to authenticate.
25
+ # If not provided it will default to [:display_name].
26
+ config.auth_facts = [:display_name, :email_address]
27
+
28
+ # Defines the model class name to be used for persistance. The active
29
+ # model provided must have `selfid` and `token` attributes.
30
+ # Note this config entry is optional and defaults to `User`.
31
+ config.session_class_name = 'User'
32
+
33
+ # Defines the path to redirect the user when authentication succeeds.
34
+ # Optional entry defaulting to '/'.
35
+ config.authenticated_path = '/'
36
+
37
+ # In case you want to persist the authentication returning facts, you
38
+ # can provide a map for the fact_name and the ActiveModel object property.
39
+ #
40
+ # This is an optional entry and you only need to provide it if you're intending
41
+ # to request and store facts during the authentication process with
42
+ # config.auth_facts
43
+ config.fact_mapping = { display_name: :name }
44
+ end
45
+ end
@@ -0,0 +1,69 @@
1
+ # desc "Explaining what the task does"
2
+ # task :self-auth-rails do
3
+ # # Task goes here
4
+ # end
5
+
6
+ desc 'Creates the self-auth-rails initializer on your application'
7
+ namespace :self_auth_rails do
8
+ task :init do
9
+ # Getting model to be used as authentication
10
+ puts "What model do you want to use for authentication? (Defaults to \"User\")"
11
+ model_name = STDIN.gets.strip
12
+ model_name = 'User' if model_name.empty?
13
+
14
+ log 'Generating migrations'
15
+ if Object.const_defined? model_name
16
+ log "#{model_name} already initialized, adding necessary columns"
17
+ system "SKIP_SELF=1 bin/rails g migration add_auth_to_#{model_name.downcase}s selfid:string name:string token:string"
18
+ else
19
+ system "SKIP_SELF=1 bin/rails g model #{model_name.downcase} selfid:string name:string token:string"
20
+ end
21
+ log 'migration file created!'
22
+
23
+ # Adding initializer to your application
24
+ log 'Creating self-auth-rails initializer from template'
25
+ body = File.read(__FILE__.gsub('self_auth_rails_tasks.rake', 'initializer.rb.tpl'))
26
+ body.gsub!("{{AuthModel}}", model_name)
27
+ File.open("#{Dir.pwd}/config/initializers/self_auth_rails.rb", 'w') { |file| file << body }
28
+ log 'config/initializers/self_auth_rails.rb created'
29
+
30
+ # Adding routes to your application
31
+ puts "What alias do you want to use to mount this rails engine? (Defaults to \"auth\")"
32
+ as = STDIN.gets.strip
33
+ as = 'auth' if as.empty?
34
+
35
+ log 'Adding routes to config/routes.rb'
36
+ file_path = "#{Dir.pwd}/config/routes.rb"
37
+ body = File.read(file_path)
38
+ if body.include? 'SelfAuthRails'
39
+ log 'skipping... (self-auth-rails routes already added)'
40
+ else
41
+ pattern = 'Rails.application.routes.draw do'
42
+ content = body.gsub(pattern, "#{pattern}\n # Mounting self-auth-rails engine\n mount SelfAuthRails::Engine => \"/auth\", as: \"#{as}\"")
43
+ File.open(file_path, 'w') { |file| file << content }
44
+ log 'self-auth-rails routes added'
45
+ end
46
+
47
+ puts 'Adding assets headers'
48
+ assets_file_path = "#{Dir.pwd}/app/views/layouts/application.html.erb"
49
+ body = File.read(assets_file_path)
50
+ header1 = '<%= javascript_include_tag "self-auth-rails/manifest" %>'
51
+ header2 = '<%= stylesheet_link_tag "self-auth-rails/application.css" %>'
52
+ body.gsub!("</head>", "#{header1}\n\t#{header2}\n\t</head>")
53
+ File.open(assets_file_path, 'w') { |file| file << body }
54
+ log 'asset headers set'
55
+
56
+ manifest_file_path = "#{Dir.pwd}/app/assets/config/manifest.js"
57
+ body = File.read(manifest_file_path)
58
+ body += "\n//= link self-auth-rails/manifest.js"
59
+ File.open(manifest_file_path, 'w') { |file| file << body }
60
+
61
+ log 'All set!'
62
+ puts 'Remember to run `bin/rails db:migrate` to create your authentication table'
63
+ end
64
+ end
65
+
66
+ def log(str)
67
+ str = "[self_auth_rails::init] #{str}"
68
+ puts "\e[#{36}m#{str}\e[0m"
69
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: self-auth-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Adrià Cidre
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-08-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '7.0'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 7.0.2.3
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '7.0'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 7.0.2.3
33
+ - !ruby/object:Gem::Dependency
34
+ name: selfsdk
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.0'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 0.0.200
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '0.0'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 0.0.200
53
+ description: Provides an out of the box implementation for self authentication
54
+ email:
55
+ - 593270+adriacidre@users.noreply.github.com
56
+ executables: []
57
+ extensions: []
58
+ extra_rdoc_files: []
59
+ files:
60
+ - MIT-LICENSE
61
+ - README.md
62
+ - Rakefile
63
+ - app/assets/config/self-auth-rails/app.js
64
+ - app/assets/config/self-auth-rails/cable.js
65
+ - app/assets/config/self-auth-rails/channels/auth.js
66
+ - app/assets/config/self-auth-rails/channels/messages_channel.js
67
+ - app/assets/config/self-auth-rails/cookies.js
68
+ - app/assets/config/self-auth-rails/ki.js
69
+ - app/assets/config/self-auth-rails/manifest.js
70
+ - app/assets/stylesheets/self-auth-rails/application.css
71
+ - app/channels/application_cable/channel.rb
72
+ - app/channels/application_cable/connection.rb
73
+ - app/channels/messages_channel.rb
74
+ - app/controllers/self_auth_rails/application_controller.rb
75
+ - app/controllers/self_auth_rails/sessions_controller.rb
76
+ - app/helpers/self_auth_rails/application_helper.rb
77
+ - app/helpers/self_auth_rails/sessions_helper.rb
78
+ - app/jobs/self_auth_rails/application_job.rb
79
+ - app/mailers/self_auth_rails/application_mailer.rb
80
+ - app/models/self_auth_rails/application_record.rb
81
+ - app/services/self_auth_response_manager_service.rb
82
+ - app/views/self_auth_rails/sessions/create.html.erb
83
+ - app/views/self_auth_rails/sessions/new.html.erb
84
+ - config/routes.rb
85
+ - lib/self-auth-rails.rb
86
+ - lib/self-auth-rails/engine.rb
87
+ - lib/self-auth-rails/version.rb
88
+ - lib/tasks/initializer.rb.tpl
89
+ - lib/tasks/self_auth_rails_tasks.rake
90
+ homepage: https://joinself.com
91
+ licenses:
92
+ - MIT
93
+ metadata:
94
+ homepage_uri: https://joinself.com
95
+ source_code_uri: https://joinself.com
96
+ changelog_uri: https://joinself.com/changelog
97
+ post_install_message:
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubygems_version: 3.1.6
113
+ signing_key:
114
+ specification_version: 4
115
+ summary: Joinself rails authentication gem
116
+ test_files: []