From f36fbf0f5919d9ba578a308ef1a3c3b6a7931901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=92=BC=E6=99=82=E5=BC=A6=E4=B9=9F?= Date: Sat, 6 May 2017 11:15:55 +0800 Subject: [PATCH] Initialize project --- .gitignore | 7 + Gemfile | 14 ++ Gemfile.lock | 143 ++++++++++++++++++ MIT-LICENSE | 20 +++ README.md | 28 ++++ Rakefile | 36 +++++ .../lets_encrypt/application_controller.rb | 5 + .../lets_encrypt/verifications_controller.rb | 25 +++ app/jobs/lets_encrypt/application_job.rb | 4 + .../lets_encrypt/certificate_issuable.rb | 37 +++++ .../lets_encrypt/certificate_verifiable.rb | 66 ++++++++ app/models/lets_encrypt/application_record.rb | 5 + app/models/lets_encrypt/certificate.rb | 21 +++ bin/rails | 13 ++ config/routes.rb | 3 + ...165114_create_lets_encrypt_certificates.rb | 17 +++ lib/letsencrypt.rb | 52 +++++++ lib/letsencrypt/engine.rb | 9 ++ lib/letsencrypt/logger_proxy.rb | 37 +++++ lib/letsencrypt/version.rb | 3 + lib/rails-letsencrypt.rb | 1 + lib/tasks/lets_encrypt_tasks.rake | 4 + rails-letsencrypt.gemspec | 27 ++++ .../verifications_controller_spec.rb | 7 + .../lets_encrypt/verifications_helper_spec.rb | 17 +++ spec/models/lets_encrypt/certificate_spec.rb | 7 + 26 files changed, 608 insertions(+) create mode 100644 .gitignore create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 MIT-LICENSE create mode 100644 README.md create mode 100644 Rakefile create mode 100644 app/controllers/lets_encrypt/application_controller.rb create mode 100644 app/controllers/lets_encrypt/verifications_controller.rb create mode 100644 app/jobs/lets_encrypt/application_job.rb create mode 100644 app/models/concerns/lets_encrypt/certificate_issuable.rb create mode 100644 app/models/concerns/lets_encrypt/certificate_verifiable.rb create mode 100644 app/models/lets_encrypt/application_record.rb create mode 100644 app/models/lets_encrypt/certificate.rb create mode 100755 bin/rails create mode 100644 config/routes.rb create mode 100644 db/migrate/20170505165114_create_lets_encrypt_certificates.rb create mode 100644 lib/letsencrypt.rb create mode 100644 lib/letsencrypt/engine.rb create mode 100644 lib/letsencrypt/logger_proxy.rb create mode 100644 lib/letsencrypt/version.rb create mode 100644 lib/rails-letsencrypt.rb create mode 100644 lib/tasks/lets_encrypt_tasks.rake create mode 100644 rails-letsencrypt.gemspec create mode 100644 spec/controllers/lets_encrypt/verifications_controller_spec.rb create mode 100644 spec/helpers/lets_encrypt/verifications_helper_spec.rb create mode 100644 spec/models/lets_encrypt/certificate_spec.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..71f82cb --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.bundle/ +log/*.log +pkg/ +test/dummy/db/*.sqlite3 +test/dummy/db/*.sqlite3-journal +test/dummy/log/*.log +test/dummy/tmp/ diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..257b308 --- /dev/null +++ b/Gemfile @@ -0,0 +1,14 @@ +source 'https://rubygems.org' + +# Declare your gem's dependencies in lets_encrypt.gemspec. +# Bundler will treat runtime dependencies like base dependencies, and +# development dependencies will be added by default to the :development group. +gemspec + +# Declare any dependencies that are still in development here instead of in +# your gemspec. These might include edge Rails or gems from your path or +# Git. Remember to move these dependencies to your gemspec before releasing +# your gem to rubygems.org. + +# To use a debugger +# gem 'byebug', group: [:development, :test] diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..14dbc50 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,143 @@ +PATH + remote: . + specs: + rails-letsencrypt (0.1.0) + acme-client + rails (~> 5.1.0) + +GEM + remote: https://rubygems.org/ + specs: + acme-client (0.5.5) + faraday (~> 0.9, >= 0.9.1) + actioncable (5.1.0) + actionpack (= 5.1.0) + nio4r (~> 2.0) + websocket-driver (~> 0.6.1) + actionmailer (5.1.0) + actionpack (= 5.1.0) + actionview (= 5.1.0) + activejob (= 5.1.0) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.1.0) + actionview (= 5.1.0) + activesupport (= 5.1.0) + rack (~> 2.0) + rack-test (~> 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.1.0) + activesupport (= 5.1.0) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.1.0) + activesupport (= 5.1.0) + globalid (>= 0.3.6) + activemodel (5.1.0) + activesupport (= 5.1.0) + activerecord (5.1.0) + activemodel (= 5.1.0) + activesupport (= 5.1.0) + arel (~> 8.0) + activesupport (5.1.0) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (~> 0.7) + minitest (~> 5.1) + tzinfo (~> 1.1) + arel (8.0.0) + builder (3.2.3) + concurrent-ruby (1.0.5) + diff-lcs (1.3) + erubi (1.6.0) + faraday (0.12.1) + multipart-post (>= 1.2, < 3) + globalid (0.4.0) + activesupport (>= 4.2.0) + i18n (0.8.1) + loofah (2.0.3) + nokogiri (>= 1.5.9) + mail (2.6.5) + mime-types (>= 1.16, < 4) + method_source (0.8.2) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) + mini_portile2 (2.1.0) + minitest (5.10.1) + multipart-post (2.0.0) + nio4r (2.0.0) + nokogiri (1.7.1) + mini_portile2 (~> 2.1.0) + rack (2.0.1) + rack-test (0.6.3) + rack (>= 1.0) + rails (5.1.0) + actioncable (= 5.1.0) + actionmailer (= 5.1.0) + actionpack (= 5.1.0) + actionview (= 5.1.0) + activejob (= 5.1.0) + activemodel (= 5.1.0) + activerecord (= 5.1.0) + activesupport (= 5.1.0) + bundler (>= 1.3.0, < 2.0) + railties (= 5.1.0) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.2) + activesupport (>= 4.2.0, < 6.0) + nokogiri (~> 1.6) + rails-html-sanitizer (1.0.3) + loofah (~> 2.0) + railties (5.1.0) + actionpack (= 5.1.0) + activesupport (= 5.1.0) + method_source + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rake (12.0.0) + rspec-core (3.5.4) + rspec-support (~> 3.5.0) + rspec-expectations (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-mocks (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-rails (3.5.2) + actionpack (>= 3.0) + activesupport (>= 3.0) + railties (>= 3.0) + rspec-core (~> 3.5.0) + rspec-expectations (~> 3.5.0) + rspec-mocks (~> 3.5.0) + rspec-support (~> 3.5.0) + rspec-support (3.5.0) + sprockets (3.7.1) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.0) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + sqlite3 (1.3.13) + thor (0.19.4) + thread_safe (0.3.6) + tzinfo (1.2.3) + thread_safe (~> 0.1) + websocket-driver (0.6.5) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.2) + +PLATFORMS + ruby + +DEPENDENCIES + rails-letsencrypt! + rspec-rails + sqlite3 + +BUNDLED WITH + 1.14.6 diff --git a/MIT-LICENSE b/MIT-LICENSE new file mode 100644 index 0000000..acddb93 --- /dev/null +++ b/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright 2017 蒼時弦也 + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..89a0e1b --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# LetsEncrypt +Short description and motivation. + +## Usage +How to use my plugin. + +## Installation +Add this line to your application's Gemfile: + +```ruby +gem 'lets_encrypt' +``` + +And then execute: +```bash +$ bundle +``` + +Or install it yourself as: +```bash +$ gem install lets_encrypt +``` + +## Contributing +Contribution directions go here. + +## License +The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..2809908 --- /dev/null +++ b/Rakefile @@ -0,0 +1,36 @@ +begin + require 'bundler/setup' +rescue LoadError + puts 'You must `gem install bundler` and `bundle install` to run rake tasks' +end + +require 'rdoc/task' + +RDoc::Task.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'LetsEncrypt' + rdoc.options << '--line-numbers' + rdoc.rdoc_files.include('README.md') + rdoc.rdoc_files.include('lib/**/*.rb') +end + +APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__) +load 'rails/tasks/engine.rake' + + +load 'rails/tasks/statistics.rake' + + + +require 'bundler/gem_tasks' + +require 'rake/testtask' + +Rake::TestTask.new(:test) do |t| + t.libs << 'test' + t.pattern = 'test/**/*_test.rb' + t.verbose = false +end + + +task default: :test diff --git a/app/controllers/lets_encrypt/application_controller.rb b/app/controllers/lets_encrypt/application_controller.rb new file mode 100644 index 0000000..831431a --- /dev/null +++ b/app/controllers/lets_encrypt/application_controller.rb @@ -0,0 +1,5 @@ +module LetsEncrypt + class ApplicationController < ActionController::Base + protect_from_forgery with: :exception + end +end diff --git a/app/controllers/lets_encrypt/verifications_controller.rb b/app/controllers/lets_encrypt/verifications_controller.rb new file mode 100644 index 0000000..1c56da2 --- /dev/null +++ b/app/controllers/lets_encrypt/verifications_controller.rb @@ -0,0 +1,25 @@ +require_dependency 'lets_encrypt/application_controller' + +module LetsEncrypt + # :nodoc: + class VerificationsController < ApplicationController + def show + return render_verification_string if certificate.present? + render plain: 'Verification not found', status: 404 + end + + protected + + def render_verification_string + render plain: certificate.verification_string + end + + def certificate + LetsEncrypt::Certificate.find_by(verification_path: filename) + end + + def filename + ".well-known/acme-challenge/#{params[:verification_path]}" + end + end +end diff --git a/app/jobs/lets_encrypt/application_job.rb b/app/jobs/lets_encrypt/application_job.rb new file mode 100644 index 0000000..0f11c8e --- /dev/null +++ b/app/jobs/lets_encrypt/application_job.rb @@ -0,0 +1,4 @@ +module LetsEncrypt + class ApplicationJob < ActiveJob::Base + end +end diff --git a/app/models/concerns/lets_encrypt/certificate_issuable.rb b/app/models/concerns/lets_encrypt/certificate_issuable.rb new file mode 100644 index 0000000..e177401 --- /dev/null +++ b/app/models/concerns/lets_encrypt/certificate_issuable.rb @@ -0,0 +1,37 @@ +module LetsEncrypt + # :nodoc: + module CertificateIssuable + extend ActiveSupport::Concern + + def issue + logger.info "Getting certificate for #{domain}" + create_certificate + # rubocop:disable Metrics/LineLength + logger.info "Certificate issued (expires on #{expires_at}, will renew after #{renew_after})" + # rubocop:enable Metrics/LineLength + true + end + + private + + def csr + csr = OpenSSL::X509::Request.new + csr.subject = OpenSSL::X509::Name.new( + [['CN', domain, OpenSSL::ASN1::UTF8STRING]] + ) + private_key = OpenSSL::PKey::RSA.new(key) + csr.public_key = private_key.public_key + csr.sign(private_key, OpenSSL::Digest::SHA256.new) + csr + end + + def create_certificate + https_cert = LetsEncrypt.client.new_certificate(csr) + self.certificate = https_cert.to_pem + self.intermediaries = https_cert.chain_to_pem + self.expires_at = https_cert.x509.not_after + self.renew_after = (expires_at - 1.month) + rand(10).days + save! + end + end +end diff --git a/app/models/concerns/lets_encrypt/certificate_verifiable.rb b/app/models/concerns/lets_encrypt/certificate_verifiable.rb new file mode 100644 index 0000000..41ec199 --- /dev/null +++ b/app/models/concerns/lets_encrypt/certificate_verifiable.rb @@ -0,0 +1,66 @@ +module LetsEncrypt + # :nodoc: + module CertificateVerifiable + extend ActiveSupport::Concern + + def verify + start_authorize + start_challenge + wait_verify_status + check_verify_status + rescue Acme::Client::Error => e + retry_on_verify_error(e) + end + + private + + def start_authorize + authorization = LetsEncrypt.client.authorize(domain: domain) + @challenge = authorization.http01 + self.verification_path = @challenge.filename + self.verification_string = @challenge.file_content + save! + end + + def start_challenge + logger.info "Attempting verification of #{domain}" + @challenge.request_verification + end + + def wait_verify_status + checks = 0 + until @challenge.verify_status != 'pending' + checks += 1 + if checks > 30 + logger.info 'Status remained at pending for 30 checks' + return false + end + sleep 1 + end + end + + def check_verify_status + unless @challenge.verify_status == 'valid' + logger.info "Status was not valid (was: #{@challenge.verify_status})" + return false + end + + true + end + + def retry_on_verify_error + @retries = 0 + if e.is_a?(Acme::Client::Error::BadNonce) && @retries < 5 + @retries += 1 + # rubocop:disable Metrics/LineLength + logger.info "Bad nounce encountered. Retrying (#{@retries} of 5 attempts)" + # rubocop:enable Metrics/LineLength + sleep 1 + verify + else + logger.info "Error: #{e.class} (#{e.message})" + return false + end + end + end +end diff --git a/app/models/lets_encrypt/application_record.rb b/app/models/lets_encrypt/application_record.rb new file mode 100644 index 0000000..94043db --- /dev/null +++ b/app/models/lets_encrypt/application_record.rb @@ -0,0 +1,5 @@ +module LetsEncrypt + class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true + end +end diff --git a/app/models/lets_encrypt/certificate.rb b/app/models/lets_encrypt/certificate.rb new file mode 100644 index 0000000..e512043 --- /dev/null +++ b/app/models/lets_encrypt/certificate.rb @@ -0,0 +1,21 @@ +module LetsEncrypt + # :nodoc: + class Certificate < ApplicationRecord + include CertificateVerifiable + include CertificateIssuable + + validates :domain, presence: true, uniqueness: true + + before_create -> { self.key = OpenSSL::PKey::RSA.new(4096).to_s } + + def get + verify && issue + end + + protected + + def logger + LetsEncrypt.logger + end + end +end diff --git a/bin/rails b/bin/rails new file mode 100755 index 0000000..19f62b9 --- /dev/null +++ b/bin/rails @@ -0,0 +1,13 @@ +#!/usr/bin/env ruby +# This command will automatically be run when you run "rails" with Rails gems +# installed from the root of your application. + +ENGINE_ROOT = File.expand_path('../..', __FILE__) +ENGINE_PATH = File.expand_path('../../lib/letsencrypt/engine', __FILE__) + +# Set up gems listed in the Gemfile. +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) + +require 'rails/all' +require 'rails/engine/commands' diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..292ec51 --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,3 @@ +LetsEncrypt::Engine.routes.draw do + get '/acme-challenge/:verification_path', to: 'verifications#show' +end diff --git a/db/migrate/20170505165114_create_lets_encrypt_certificates.rb b/db/migrate/20170505165114_create_lets_encrypt_certificates.rb new file mode 100644 index 0000000..26ee496 --- /dev/null +++ b/db/migrate/20170505165114_create_lets_encrypt_certificates.rb @@ -0,0 +1,17 @@ +class CreateLetsEncryptCertificates < ActiveRecord::Migration[5.1] + def change + create_table :lets_encrypt_certificates do |t| + t.string :domain + t.text :certificate, limit: 65535 + t.text :intermediaries, limit: 65535 + t.text :key, limit: 65535 + t.datetime :expires_at + t.datetime :renew_after + t.string :verification_path + t.string :verification_string + + t.index :domain + t.timestamps + end + end +end diff --git a/lib/letsencrypt.rb b/lib/letsencrypt.rb new file mode 100644 index 0000000..aff1620 --- /dev/null +++ b/lib/letsencrypt.rb @@ -0,0 +1,52 @@ +require 'openssl' +require 'acme-client' +require 'letsencrypt/engine' +require 'letsencrypt/logger_proxy' + +# :nodoc: +module LetsEncrypt + def self.client + @client ||= ::Acme::Client.new(private_key: private_key, endpoint: endpoint) + end + + def self.private_key + # TODO: Add options to retrieve key + @private_key ||= if private_key_path.exist? + OpenSSL::PKey::RSA.new(File.open(private_key_path)) + else + generate_private_key + end + end + + def self.endpoint + @endpoint ||= if Rails.env.production? + 'https://acme-v01.api.letsencrypt.org/' + else + 'https://acme-staging.api.letsencrypt.org' + end + end + + def self.register(email) + registration = client.register(contact: "mailto:#{email}") + logger.info "Successfully registered private key with address #{email}" + registration.agree_terms + logger.info 'Terms have been accepted' + true + end + + def self.private_key_path + # TODO: Add options for specify path + Rails.root.join('config', 'letsencrypt.key') + end + + def self.generate_private_key + key = OpenSSL::PKey::RSA.new(4096) + File.open(private_key_path, 'w') { |f| f.write(key.to_s) } + logger.info "Created new private key for Let's Encrypt" + key + end + + def self.logger + @logger ||= LoggerProxy.new(Rails.logger, tags: ['LetsEncrypt']) + end +end diff --git a/lib/letsencrypt/engine.rb b/lib/letsencrypt/engine.rb new file mode 100644 index 0000000..ede5d54 --- /dev/null +++ b/lib/letsencrypt/engine.rb @@ -0,0 +1,9 @@ +module LetsEncrypt + # :nodoc: + class Engine < ::Rails::Engine + isolate_namespace LetsEncrypt + engine_name :letsencrypt + + config.generators.test_framework :rspec + end +end diff --git a/lib/letsencrypt/logger_proxy.rb b/lib/letsencrypt/logger_proxy.rb new file mode 100644 index 0000000..33bf89b --- /dev/null +++ b/lib/letsencrypt/logger_proxy.rb @@ -0,0 +1,37 @@ +module LetsEncrypt + # :nodoc: + class LoggerProxy + attr_reader :tags + + def initialize(logger, tags:) + @logger = logger + @tags = tags.flatten + end + + def add_tags(*tags) + @tags += tags.flatten + @tags = @tags.uniq + end + + def tag(logger) + if logger.respond_to?(:tagged) + current_tags = tags - logger.formatter.current_tags + logger.tagged(*current_tags) { yield } + else + yield + end + end + + %i[debug info warn error fatal unknown].each do |severity| + define_method(severity) do |message| + log severity, message + end + end + + private + + def log(type, message) + tag(@logger) { @logger.send type, message } + end + end +end diff --git a/lib/letsencrypt/version.rb b/lib/letsencrypt/version.rb new file mode 100644 index 0000000..68caa65 --- /dev/null +++ b/lib/letsencrypt/version.rb @@ -0,0 +1,3 @@ +module LetsEncrypt + VERSION = '0.1.0' +end diff --git a/lib/rails-letsencrypt.rb b/lib/rails-letsencrypt.rb new file mode 100644 index 0000000..9e83314 --- /dev/null +++ b/lib/rails-letsencrypt.rb @@ -0,0 +1 @@ +require 'letsencrypt' diff --git a/lib/tasks/lets_encrypt_tasks.rake b/lib/tasks/lets_encrypt_tasks.rake new file mode 100644 index 0000000..c23b3b0 --- /dev/null +++ b/lib/tasks/lets_encrypt_tasks.rake @@ -0,0 +1,4 @@ +# desc "Explaining what the task does" +# task :lets_encrypt do +# # Task goes here +# end diff --git a/rails-letsencrypt.gemspec b/rails-letsencrypt.gemspec new file mode 100644 index 0000000..234d53c --- /dev/null +++ b/rails-letsencrypt.gemspec @@ -0,0 +1,27 @@ +$:.push File.expand_path("../lib", __FILE__) + +# Maintain your gem's version: +require "letsencrypt/version" + +# Describe your gem and declare its dependencies: +Gem::Specification.new do |s| + s.name = "rails-letsencrypt" + s.version = LetsEncrypt::VERSION + s.authors = ["蒼時弦也"] + s.email = ["elct9620@frost.tw"] + s.homepage = "https://github.com/elct9620/rails-letsencrypt" + s.summary = "The Let's Encrypt certificate manager for rails" + s.description = "The Let's Encrypt certificate manager for rails" + s.license = "MIT" + + s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"] + s.require_paths = ["lib"] + + s.add_dependency "rails", "~> 5.1.0" + s.add_dependency "acme-client" + + s.add_development_dependency "sqlite3" + s.add_development_dependency "rspec-rails" +end + + diff --git a/spec/controllers/lets_encrypt/verifications_controller_spec.rb b/spec/controllers/lets_encrypt/verifications_controller_spec.rb new file mode 100644 index 0000000..e0c6c6c --- /dev/null +++ b/spec/controllers/lets_encrypt/verifications_controller_spec.rb @@ -0,0 +1,7 @@ +require 'rails_helper' + +module LetsEncrypt + RSpec.describe VerificationsController, type: :controller do + + end +end diff --git a/spec/helpers/lets_encrypt/verifications_helper_spec.rb b/spec/helpers/lets_encrypt/verifications_helper_spec.rb new file mode 100644 index 0000000..98aa99d --- /dev/null +++ b/spec/helpers/lets_encrypt/verifications_helper_spec.rb @@ -0,0 +1,17 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the VerificationsHelper. For example: +# +# describe VerificationsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +module LetsEncrypt + RSpec.describe VerificationsHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" + end +end diff --git a/spec/models/lets_encrypt/certificate_spec.rb b/spec/models/lets_encrypt/certificate_spec.rb new file mode 100644 index 0000000..df5132f --- /dev/null +++ b/spec/models/lets_encrypt/certificate_spec.rb @@ -0,0 +1,7 @@ +require 'rails_helper' + +module LetsEncrypt + RSpec.describe Certificate, type: :model do + pending "add some examples to (or delete) #{__FILE__}" + end +end