self-auth-rails 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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: []