authpls 0.1.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +53 -0
- data/Rakefile +12 -0
- data/lib/authpls/railtie.rb +5 -0
- data/lib/authpls/version.rb +5 -0
- data/lib/authpls.rb +9 -0
- data/lib/generators/authpls/scaffold/scaffold_generator.rb +82 -0
- data/lib/generators/templates/authentication_concern.rb.tt +59 -0
- data/lib/generators/templates/cleanup_expired_sessions_job.rb.tt +7 -0
- data/lib/generators/templates/current_model.rb.tt +4 -0
- data/lib/generators/templates/passwords_controller.rb.tt +34 -0
- data/lib/generators/templates/passwords_mailer.rb.tt +8 -0
- data/lib/generators/templates/profiles_controller.rb.tt +40 -0
- data/lib/generators/templates/registrations_controller.rb.tt +62 -0
- data/lib/generators/templates/session_model.rb.tt +6 -0
- data/lib/generators/templates/session_serializer.rb.tt +5 -0
- data/lib/generators/templates/sessions_controller.rb.tt +27 -0
- data/lib/generators/templates/sessions_migration.rb.tt +15 -0
- data/lib/generators/templates/signup_token_model.rb.tt +6 -0
- data/lib/generators/templates/signup_token_serializer.rb.tt +5 -0
- data/lib/generators/templates/signup_tokens_controller.rb.tt +24 -0
- data/lib/generators/templates/signup_tokens_migration.rb.tt +14 -0
- data/lib/generators/templates/user_model.rb.tt +9 -0
- data/lib/generators/templates/user_serializer.rb.tt +6 -0
- data/lib/generators/templates/users_controller.rb.tt +62 -0
- data/lib/generators/templates/users_migration.rb.tt +12 -0
- data/sig/authpls.rbs +4 -0
- metadata +99 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: bb1f8ab6bd4cc603980ffa7f719a43f5df8396a1a1ef682bce1948483266bf71
|
|
4
|
+
data.tar.gz: 840873ee17ff22053532747c7c49f1e0191804890d40f7f70edda3d8f4d996e2
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 778244f8ccad0b8f30e5ac9970a753b764a56a1bf88c860d83693dc41eebe6114209898fcdbad5b842cd4dc5326ac1a237a31c78691a22a6376980b18cda17bb
|
|
7
|
+
data.tar.gz: 33133d57aafcdc8db64f747154bfddc453c9030534fed089b3aed60dd114ef19e1255095ed320afcabbe1aceb1f14a6ced57c7c670e6b780c2c21ab5792b9962
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alex Locke
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Authpls
|
|
2
|
+
|
|
3
|
+
A Ruby gem that scaffolds a complete API-only authentication and session system for Rails applications.
|
|
4
|
+
|
|
5
|
+
## What It Generates
|
|
6
|
+
|
|
7
|
+
Running the generator creates:
|
|
8
|
+
|
|
9
|
+
- **User model** with `has_secure_password` and email normalization
|
|
10
|
+
- **Session model** with secure token authentication and expiry
|
|
11
|
+
- **Invite-only registration** via admin-issued signup tokens
|
|
12
|
+
- **Password resets** with mailer
|
|
13
|
+
- **Admin gating** for protected endpoints
|
|
14
|
+
- **Rate limiting** on auth endpoints
|
|
15
|
+
- **Session cleanup job** for expired sessions
|
|
16
|
+
- All controllers, migrations, models, serializers, and routes under an `Auth::` namespace
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
Add to your Gemfile:
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
gem "authpls"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then run:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
bundle install
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
Generate the auth scaffold:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
rails generate authpls:scaffold
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Then run migrations:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
rails db:migrate
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Requirements
|
|
47
|
+
|
|
48
|
+
- Ruby >= 3.2
|
|
49
|
+
- Rails (API mode)
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
MIT
|
data/Rakefile
ADDED
data/lib/authpls.rb
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "rails/generators/active_record"
|
|
5
|
+
|
|
6
|
+
module Authpls
|
|
7
|
+
module Generators
|
|
8
|
+
# Generates a complete authentication implementation
|
|
9
|
+
class ScaffoldGenerator < Rails::Generators::Base
|
|
10
|
+
include ActiveRecord::Generators::Migration
|
|
11
|
+
source_root File.expand_path("../../templates", __dir__)
|
|
12
|
+
|
|
13
|
+
def copy_application_controller_concerns
|
|
14
|
+
inject_into_file "app/controllers/application_controller.rb", after: "ActionController::API\n" do
|
|
15
|
+
<<~RUBY
|
|
16
|
+
include ActionController::HttpAuthentication::Token::ControllerMethods
|
|
17
|
+
include Auth::Authentication
|
|
18
|
+
rescue_from ArgumentError, with: :handle_argument_errors
|
|
19
|
+
rescue_from ActiveRecord::RecordNotFound do
|
|
20
|
+
render json: { error: 'Not found' }, status: :not_found
|
|
21
|
+
end
|
|
22
|
+
RATE_LIMIT_RESPONSE = -> { render json: { error: 'Try again later' }, status: :too_many_requests }
|
|
23
|
+
def handle_argument_errors(e)
|
|
24
|
+
logger.error e.full_message
|
|
25
|
+
render json: { error: e.message }, status: :unprocessable_content
|
|
26
|
+
end
|
|
27
|
+
RUBY
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def copy_controllers
|
|
32
|
+
template "users_controller.rb.tt", "app/controllers/auth/users_controller.rb"
|
|
33
|
+
template "sessions_controller.rb.tt", "app/controllers/auth/sessions_controller.rb"
|
|
34
|
+
template "profiles_controller.rb.tt", "app/controllers/auth/profiles_controller.rb"
|
|
35
|
+
template "passwords_controller.rb.tt", "app/controllers/auth/passwords_controller.rb"
|
|
36
|
+
template "registrations_controller.rb.tt", "app/controllers/auth/registrations_controller.rb"
|
|
37
|
+
template "signup_tokens_controller.rb.tt", "app/controllers/auth/signup_tokens_controller.rb"
|
|
38
|
+
template "authentication_concern.rb.tt", "app/controllers/concerns/auth/authentication.rb"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def copy_migrations
|
|
42
|
+
migration_template "users_migration.rb.tt", "db/migrate/create_users.rb"
|
|
43
|
+
migration_template "sessions_migration.rb.tt", "db/migrate/create_sessions.rb"
|
|
44
|
+
migration_template "signup_tokens_migration.rb.tt", "db/migrate/create_signup_tokens.rb"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def copy_models
|
|
48
|
+
template "user_model.rb.tt", "app/models/auth/user.rb"
|
|
49
|
+
template "session_model.rb.tt", "app/models/auth/session.rb"
|
|
50
|
+
template "signup_token_model.rb.tt", "app/models/auth/signup_token.rb"
|
|
51
|
+
template "current_model.rb.tt", "app/models/current.rb"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def copy_jobs
|
|
55
|
+
template "cleanup_expired_sessions_job.rb.tt", "app/jobs/auth/cleanup_expired_sessions_job.rb"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def copy_mailers
|
|
59
|
+
template "passwords_mailer.rb.tt", "app/mailers/auth/passwords_mailer.rb"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def copy_serializers
|
|
63
|
+
template "user_serializer.rb.tt", "app/serializers/auth/user_serializer.rb"
|
|
64
|
+
template "session_serializer.rb.tt", "app/serializers/auth/session_serializer.rb"
|
|
65
|
+
template "signup_token_serializer.rb.tt", "app/serializers/auth/signup_token_serializer.rb"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def copy_routes
|
|
69
|
+
route <<~RUBY
|
|
70
|
+
namespace :auth do
|
|
71
|
+
resource :session, only: %i[create destroy]
|
|
72
|
+
resource :registration, only: %i[create]
|
|
73
|
+
resource :profile, only: %i[show update destroy]
|
|
74
|
+
resources :users, only: %i[index show update destroy]
|
|
75
|
+
resources :passwords, only: %i[create update]
|
|
76
|
+
resources :signup_tokens, only: %i[create]
|
|
77
|
+
end
|
|
78
|
+
RUBY
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module Auth
|
|
2
|
+
module Authentication
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
before_action :require_authentication
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class_methods do
|
|
10
|
+
def allow_unauthenticated_access(**options)
|
|
11
|
+
skip_before_action :require_authentication, **options
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def require_admin_access(**options)
|
|
15
|
+
before_action :verify_is_admin, **options
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def require_authentication
|
|
22
|
+
resume_session || render_unauthorized
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def render_unauthorized
|
|
26
|
+
render json: { error: 'Unauthorized' }, status: 401
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def resume_session
|
|
30
|
+
Current.session ||= find_session_by_token
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def find_session_by_token
|
|
34
|
+
authenticate_with_http_token do |token, _options|
|
|
35
|
+
Session.where(expires_at: Time.current..).find_by(token: token)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def start_new_session_for(user)
|
|
40
|
+
user.sessions.create!(
|
|
41
|
+
user_agent: request.user_agent,
|
|
42
|
+
ip_address: request.remote_ip,
|
|
43
|
+
expires_at: 6.hours.from_now
|
|
44
|
+
).tap { |session| Current.session = session }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def terminate_session
|
|
48
|
+
Current.session.destroy
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def verify_is_admin
|
|
52
|
+
render_forbidden unless Current.session.user.admin?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def render_forbidden
|
|
56
|
+
render json: { error: 'Forbidden' }, status: 403
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Auth
|
|
2
|
+
class PasswordsController < ApplicationController
|
|
3
|
+
allow_unauthenticated_access only: %i[create update]
|
|
4
|
+
before_action :set_user_by_token, only: %i[update]
|
|
5
|
+
rate_limit to: 10, within: 3.minutes, only: :create, with: ApplicationController::RATE_LIMIT_RESPONSE
|
|
6
|
+
|
|
7
|
+
def create
|
|
8
|
+
if (user = User.find_by(email_address: params[:email_address]))
|
|
9
|
+
PasswordsMailer.reset(user).deliver_later
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
render json: { message: 'Password reset instructions sent' }, status: 202
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def update
|
|
16
|
+
if @user.update(params.permit(:password, :password_confirmation))
|
|
17
|
+
@user.sessions.destroy_all
|
|
18
|
+
render json: { message: 'Password has been reset.' }, status: 200
|
|
19
|
+
else
|
|
20
|
+
render json: { errors: @user.errors.full_messages }, status: 422
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def set_user_by_token
|
|
27
|
+
@user = User.find_by_password_reset_token!(params[:token])
|
|
28
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
|
29
|
+
render json: {
|
|
30
|
+
errors: ['Password reset link is invalid or has expired.']
|
|
31
|
+
}, status: 401
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Auth
|
|
2
|
+
class ProfilesController < ApplicationController
|
|
3
|
+
rate_limit to: 10, within: 5.minutes, with: ApplicationController::RATE_LIMIT_RESPONSE
|
|
4
|
+
|
|
5
|
+
def show
|
|
6
|
+
render json: serializer.serialize_to_json(Current.session.user)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def update
|
|
10
|
+
user = Current.session.user
|
|
11
|
+
user.attributes = attributes
|
|
12
|
+
|
|
13
|
+
if user.save
|
|
14
|
+
render json: serializer.serialize_to_json(user)
|
|
15
|
+
else
|
|
16
|
+
render json: user.errors, status: :unprocessable_content
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def destroy
|
|
21
|
+
if Current.session.user.destroy
|
|
22
|
+
head :no_content
|
|
23
|
+
else
|
|
24
|
+
head :unprocessable_content
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def attributes
|
|
31
|
+
params.expect(profile: %i[email_address])
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def serializer
|
|
35
|
+
UserSerializer.new(
|
|
36
|
+
only: { instance: %i[id email_address admin] }
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module Auth
|
|
2
|
+
class RegistrationsController < ApplicationController
|
|
3
|
+
allow_unauthenticated_access only: :create
|
|
4
|
+
rate_limit to: 10, within: 3.minutes, only: :create, with: ApplicationController::RATE_LIMIT_RESPONSE
|
|
5
|
+
|
|
6
|
+
# Registers a user, requires a valid signup token from an admin
|
|
7
|
+
# Creates a session upon successful registration
|
|
8
|
+
# Renders token, + account info on success
|
|
9
|
+
# Renders error message on invalid request or unique constraint violation
|
|
10
|
+
def create
|
|
11
|
+
token = validate_signup_token
|
|
12
|
+
return unless token
|
|
13
|
+
|
|
14
|
+
user = register_user(token)
|
|
15
|
+
start_new_session_for user
|
|
16
|
+
render json: { token: Current.session.token, user: serializer.serialize(user) }, status: 201
|
|
17
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
18
|
+
render json: { error: e.record.errors.full_messages.join(', ') }, status: 422
|
|
19
|
+
rescue ActiveRecord::RecordNotUnique
|
|
20
|
+
render json: { error: 'There was a problem creating your account' }, status: 422
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
# Validates a signup token
|
|
26
|
+
# Checks token exists, is not expired, and has not already been used
|
|
27
|
+
# Returns the token if valid, returns nil + renders error if invalid
|
|
28
|
+
def validate_signup_token
|
|
29
|
+
token = SignupToken.find_by(token: params[:signup_token])
|
|
30
|
+
return render_error('Unauthorised', :unauthorized) unless token
|
|
31
|
+
return render_error('Token Expired', :unauthorized) if token.expires_at < Time.current
|
|
32
|
+
return render_error('Invalid Token', :unauthorized) if token.used_at
|
|
33
|
+
|
|
34
|
+
token
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Creates a user with the request params, updates the token to used
|
|
38
|
+
def register_user(token)
|
|
39
|
+
user = User.new(attributes)
|
|
40
|
+
User.transaction do
|
|
41
|
+
user.save!
|
|
42
|
+
token.update!(used_at: Time.current, user: user)
|
|
43
|
+
end
|
|
44
|
+
user
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def render_error(msg, status)
|
|
48
|
+
render(json: { error: msg }, status:)
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def attributes
|
|
53
|
+
params.expect(registration: %i[email_address password password_confirmation])
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def serializer
|
|
57
|
+
UserSerializer.new(
|
|
58
|
+
only: { instance: %i[id email_address created_at admin] }
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Auth
|
|
2
|
+
class SessionsController < ApplicationController
|
|
3
|
+
allow_unauthenticated_access only: %i[create]
|
|
4
|
+
rate_limit to: 10, within: 3.minutes, only: :create, with: ApplicationController::RATE_LIMIT_RESPONSE
|
|
5
|
+
|
|
6
|
+
def create
|
|
7
|
+
user = User.authenticate_by(session_params)
|
|
8
|
+
if user
|
|
9
|
+
start_new_session_for user
|
|
10
|
+
render json: { token: Current.session.token }, status: 201
|
|
11
|
+
else
|
|
12
|
+
render json: { error: 'Invalid email address or password' }, status: 401
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def destroy
|
|
17
|
+
terminate_session
|
|
18
|
+
render json: { message: 'Logged out' }, status: 200
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def session_params
|
|
24
|
+
params.require(:session).permit(:email_address, :password)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class CreateSessions < ActiveRecord::Migration[8.1]
|
|
2
|
+
def change
|
|
3
|
+
create_table :sessions do |t|
|
|
4
|
+
t.references :user, null: false, foreign_key: true
|
|
5
|
+
t.string :ip_address
|
|
6
|
+
t.string :user_agent
|
|
7
|
+
t.datetime :expires_at
|
|
8
|
+
|
|
9
|
+
t.timestamps
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
add_column :sessions, :token, :string
|
|
13
|
+
add_index :sessions, :token, unique: true
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Auth
|
|
2
|
+
class SignupTokensController < ApplicationController
|
|
3
|
+
require_admin_access
|
|
4
|
+
rate_limit to: 20, within: 1.hour, with: ApplicationController::RATE_LIMIT_RESPONSE
|
|
5
|
+
|
|
6
|
+
# Create a signup token to be used to create an account
|
|
7
|
+
def create
|
|
8
|
+
signup_token = SignupToken.new(expires_at: 24.hours.from_now)
|
|
9
|
+
if signup_token.save
|
|
10
|
+
render json: serializer.serialize_to_json(signup_token), status: :created
|
|
11
|
+
else
|
|
12
|
+
render json: signup_token.errors, status: :unprocessable_content
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def serializer
|
|
19
|
+
SignupTokenSerializer.new(
|
|
20
|
+
only: { instance: %i[token expires_at] }
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class CreateSignupTokens < ActiveRecord::Migration[8.1]
|
|
2
|
+
def change
|
|
3
|
+
create_table :signup_tokens do |t|
|
|
4
|
+
t.references :user, foreign_key: true
|
|
5
|
+
t.string :token, null: false
|
|
6
|
+
t.datetime :expires_at, null: false
|
|
7
|
+
t.datetime :used_at
|
|
8
|
+
|
|
9
|
+
t.timestamps
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
add_index :signup_tokens, :token, unique: true
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module Auth
|
|
2
|
+
class UsersController < ApplicationController
|
|
3
|
+
require_admin_access
|
|
4
|
+
rate_limit to: 30, within: 1.minute, with: ApplicationController::RATE_LIMIT_RESPONSE
|
|
5
|
+
|
|
6
|
+
# Get all users
|
|
7
|
+
# Renders all users and their sessions
|
|
8
|
+
# TODO: include only active sessions
|
|
9
|
+
def index
|
|
10
|
+
users = User.all.includes(:sessions)
|
|
11
|
+
render json: Panko::ArraySerializer.new(
|
|
12
|
+
users,
|
|
13
|
+
each_serializer: UserSerializer,
|
|
14
|
+
only: { instance: %i[id email_address created_at admin sessions],
|
|
15
|
+
sessions: { instance: %i[id ip_address user_agent created_at] } }
|
|
16
|
+
).to_json, status: :ok
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Get and render a user for a given id
|
|
20
|
+
def show
|
|
21
|
+
render json: serializer.serialize_to_json(user)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Update and render user for a given id with a set of attributes
|
|
25
|
+
def update
|
|
26
|
+
user.attributes = attributes
|
|
27
|
+
|
|
28
|
+
if user.save
|
|
29
|
+
render json: serializer.serialize_to_json(user)
|
|
30
|
+
else
|
|
31
|
+
render json: user.errors, status: :unprocessable_content
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Delete a user for a given id
|
|
36
|
+
def destroy
|
|
37
|
+
if user.destroy
|
|
38
|
+
head :no_content
|
|
39
|
+
else
|
|
40
|
+
head :unprocessable_content
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
# Find a user from an id param
|
|
47
|
+
def user
|
|
48
|
+
@user ||= User.find(params[:id])
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def attributes
|
|
52
|
+
params.expect(user: %i[email_address password password_confirmation])
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def serializer
|
|
56
|
+
UserSerializer.new(
|
|
57
|
+
only: { instance: %i[id email_address created_at admin sessions],
|
|
58
|
+
sessions: { instance: %i[id ip_address user_agent created_at] } }
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class CreateUsers < ActiveRecord::Migration[8.1]
|
|
2
|
+
def change
|
|
3
|
+
create_table :users do |t|
|
|
4
|
+
t.string :email_address, null: false
|
|
5
|
+
t.string :password_digest, null: false
|
|
6
|
+
t.boolean :admin, null: false, default: false
|
|
7
|
+
|
|
8
|
+
t.timestamps
|
|
9
|
+
end
|
|
10
|
+
add_index :users, :email_address, unique: true
|
|
11
|
+
end
|
|
12
|
+
end
|
data/sig/authpls.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: authpls
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Alex Locke
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: bcrypt
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '3.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '3.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: panko_serializer
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.8'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.8'
|
|
40
|
+
email:
|
|
41
|
+
- alocke322@gmail.com
|
|
42
|
+
executables: []
|
|
43
|
+
extensions: []
|
|
44
|
+
extra_rdoc_files: []
|
|
45
|
+
files:
|
|
46
|
+
- CHANGELOG.md
|
|
47
|
+
- LICENSE.txt
|
|
48
|
+
- README.md
|
|
49
|
+
- Rakefile
|
|
50
|
+
- lib/authpls.rb
|
|
51
|
+
- lib/authpls/railtie.rb
|
|
52
|
+
- lib/authpls/version.rb
|
|
53
|
+
- lib/generators/authpls/scaffold/scaffold_generator.rb
|
|
54
|
+
- lib/generators/templates/authentication_concern.rb.tt
|
|
55
|
+
- lib/generators/templates/cleanup_expired_sessions_job.rb.tt
|
|
56
|
+
- lib/generators/templates/current_model.rb.tt
|
|
57
|
+
- lib/generators/templates/passwords_controller.rb.tt
|
|
58
|
+
- lib/generators/templates/passwords_mailer.rb.tt
|
|
59
|
+
- lib/generators/templates/profiles_controller.rb.tt
|
|
60
|
+
- lib/generators/templates/registrations_controller.rb.tt
|
|
61
|
+
- lib/generators/templates/session_model.rb.tt
|
|
62
|
+
- lib/generators/templates/session_serializer.rb.tt
|
|
63
|
+
- lib/generators/templates/sessions_controller.rb.tt
|
|
64
|
+
- lib/generators/templates/sessions_migration.rb.tt
|
|
65
|
+
- lib/generators/templates/signup_token_model.rb.tt
|
|
66
|
+
- lib/generators/templates/signup_token_serializer.rb.tt
|
|
67
|
+
- lib/generators/templates/signup_tokens_controller.rb.tt
|
|
68
|
+
- lib/generators/templates/signup_tokens_migration.rb.tt
|
|
69
|
+
- lib/generators/templates/user_model.rb.tt
|
|
70
|
+
- lib/generators/templates/user_serializer.rb.tt
|
|
71
|
+
- lib/generators/templates/users_controller.rb.tt
|
|
72
|
+
- lib/generators/templates/users_migration.rb.tt
|
|
73
|
+
- sig/authpls.rbs
|
|
74
|
+
homepage: https://github.com/ARLocke322/authpls
|
|
75
|
+
licenses:
|
|
76
|
+
- MIT
|
|
77
|
+
metadata:
|
|
78
|
+
allowed_push_host: https://rubygems.org
|
|
79
|
+
homepage_uri: https://github.com/ARLocke322/authpls
|
|
80
|
+
source_code_uri: https://github.com/ARLocke322/authpls
|
|
81
|
+
changelog_uri: https://github.com/ARLocke322/authpls/blob/main/CHANGELOG.md
|
|
82
|
+
rdoc_options: []
|
|
83
|
+
require_paths:
|
|
84
|
+
- lib
|
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: 3.2.0
|
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
|
+
requirements:
|
|
92
|
+
- - ">="
|
|
93
|
+
- !ruby/object:Gem::Version
|
|
94
|
+
version: '0'
|
|
95
|
+
requirements: []
|
|
96
|
+
rubygems_version: 4.0.6
|
|
97
|
+
specification_version: 4
|
|
98
|
+
summary: A Gem that scaffolds a complete API-only authentication and session system
|
|
99
|
+
test_files: []
|