password_forge 0.0.2
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/.kiro/steering/product.md +46 -0
- data/.kiro/steering/structure.md +79 -0
- data/.kiro/steering/tech.md +68 -0
- data/CHANGELOG.md +48 -0
- data/LICENSE.txt +21 -0
- data/README.md +152 -0
- data/Rakefile +12 -0
- data/lib/password_forge/charset.rb +38 -0
- data/lib/password_forge/errors.rb +18 -0
- data/lib/password_forge/generator.rb +63 -0
- data/lib/password_forge/validation.rb +34 -0
- data/lib/password_forge/version.rb +5 -0
- data/lib/password_forge.rb +11 -0
- data/sig/password_forge.rbs +4 -0
- metadata +58 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: '04491df7a00f384358316e13cc7aefcfb779ff7b086fe0f5d4c1d8debc7cb11c'
|
|
4
|
+
data.tar.gz: 87e0e0c2b7965143d43d0e624fce83da06a591bc2c341e1dae8e280807a49af6
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ab01ba4518870bd2af66924298ba94a6509c703843216678556ba31ce1cdc31d22ecef7a5a996c0ef9cf3f2a8528130101813161d0aed0b0fe57c592082bc786
|
|
7
|
+
data.tar.gz: c573c0c3ec020471d63fb6bb3de55520bd0b5cdbd957e82755b638ea269fef69220db75a315a21e609b88492b9084fa5967a8456a580d8f74ac5ce0c777149ed
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Product
|
|
2
|
+
|
|
3
|
+
## What this is
|
|
4
|
+
|
|
5
|
+
`password_forge` is a configurable password generator distributed as a Ruby gem.
|
|
6
|
+
It generates random passwords from four selectable character sets — uppercase,
|
|
7
|
+
lowercase, numeric and special — that can be switched on or off independently.
|
|
8
|
+
All sets are enabled by default, and an error is raised if every set is
|
|
9
|
+
disabled. Randomness comes from Ruby's `SecureRandom`.
|
|
10
|
+
|
|
11
|
+
The public API mirrors the design of an existing C# / NuGet package: a
|
|
12
|
+
constructor with four boolean flags plus a length, and a dedicated exception
|
|
13
|
+
when no character set is selected.
|
|
14
|
+
|
|
15
|
+
## Goals
|
|
16
|
+
|
|
17
|
+
1. **Learning vehicle.** This is the author's first Ruby gem. Code favours
|
|
18
|
+
clarity and idiomatic Ruby over cleverness, and everything is built with TDD.
|
|
19
|
+
2. **Kiro evangelism in the Ruby world.** Beyond the gem itself, the project
|
|
20
|
+
ships a full "Kiro-native" experience for Ruby gem authors: Kiro skills,
|
|
21
|
+
project steering (this folder), hooks, and an MCP server. The repository is
|
|
22
|
+
meant to be a reference example of building and shipping a gem the Kiro way.
|
|
23
|
+
|
|
24
|
+
## Audience
|
|
25
|
+
|
|
26
|
+
- Ruby developers who need a small, dependency-free password generator.
|
|
27
|
+
- Ruby developers curious about using Kiro to build and release gems.
|
|
28
|
+
|
|
29
|
+
## Language
|
|
30
|
+
|
|
31
|
+
All repository content (code, comments, docs, commit messages) is in **English**.
|
|
32
|
+
The only exception is the author's personal development diary, which is kept
|
|
33
|
+
locally in `private-notes/` and excluded from the repository.
|
|
34
|
+
|
|
35
|
+
## Release roadmap
|
|
36
|
+
|
|
37
|
+
The project ships in small, tagged increments. Each version is merged to `main`
|
|
38
|
+
before the next begins:
|
|
39
|
+
|
|
40
|
+
- **0.0.1** — Core generator, character sets, validation, tests, docs. (done)
|
|
41
|
+
- **0.1.0** — First public release on RubyGems.org via Trusted Publishing.
|
|
42
|
+
- **0.2.0** — Kiro skills for gem authors (feature TDD, version bump, release).
|
|
43
|
+
- **0.3.0** — Project `.kiro/` folder with full steering and conventions.
|
|
44
|
+
- **0.4.0** — Kiro hooks (run specs on save, changelog reminders, etc.).
|
|
45
|
+
- **0.5.0** — A Ruby MCP server exposing password generation as a tool.
|
|
46
|
+
- **0.6.0** — A fluent/builder API layered on top of the keyword-argument API.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Structure
|
|
2
|
+
|
|
3
|
+
## Directory layout
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
password-forge-ruby-gem/ # repo root
|
|
7
|
+
├── lib/
|
|
8
|
+
│ ├── password_forge.rb # Entry point: requires all components
|
|
9
|
+
│ └── password_forge/
|
|
10
|
+
│ ├── version.rb # VERSION constant
|
|
11
|
+
│ ├── errors.rb # Error, NoCharsetSelectedError
|
|
12
|
+
│ ├── charset.rb # Charset value object (character sets + build)
|
|
13
|
+
│ ├── validation.rb # Validation module (selection + length)
|
|
14
|
+
│ └── generator.rb # Generator class + PasswordForge.generate
|
|
15
|
+
├── spec/
|
|
16
|
+
│ ├── spec_helper.rb
|
|
17
|
+
│ ├── password_forge_spec.rb # Top-level (version) spec
|
|
18
|
+
│ └── password_forge/
|
|
19
|
+
│ ├── charset_spec.rb
|
|
20
|
+
│ ├── validation_spec.rb
|
|
21
|
+
│ └── generator_spec.rb
|
|
22
|
+
├── examples/ # Runnable local demos (not packaged)
|
|
23
|
+
│ ├── smoke_test.rb # Loads the gem from source and prints samples
|
|
24
|
+
│ └── local-consumer/ # Mini "external" project using the gem via path:
|
|
25
|
+
│ ├── Gemfile
|
|
26
|
+
│ └── run.rb
|
|
27
|
+
├── docs/ # Extended documentation (not packaged)
|
|
28
|
+
│ ├── README.md # Docs index
|
|
29
|
+
│ └── local-testing.md # How to try the gem locally
|
|
30
|
+
├── sig/ # RBS type signatures
|
|
31
|
+
├── bin/ # setup + console helper scripts
|
|
32
|
+
├── .github/workflows/
|
|
33
|
+
│ ├── main.yml # CI: RSpec matrix + RuboCop
|
|
34
|
+
│ └── release.yml # Trusted Publishing on v* tags
|
|
35
|
+
├── .kiro/ # Kiro steering / skills / hooks
|
|
36
|
+
├── private-notes/ # Local-only, git-ignored (dev diary)
|
|
37
|
+
├── password_forge.gemspec
|
|
38
|
+
├── Gemfile
|
|
39
|
+
├── Rakefile
|
|
40
|
+
├── README.md
|
|
41
|
+
├── CHANGELOG.md
|
|
42
|
+
└── LICENSE.txt
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`Gemfile.lock` is generated per environment and git-ignored (see Tech), so it is
|
|
46
|
+
not part of the tracked tree above.
|
|
47
|
+
|
|
48
|
+
## Architecture
|
|
49
|
+
|
|
50
|
+
The internal design mirrors the original C# separation of concerns while
|
|
51
|
+
staying idiomatic Ruby:
|
|
52
|
+
|
|
53
|
+
- **`PasswordForge::Charset`** — a module acting as a value object. Holds the
|
|
54
|
+
frozen `UPPER`, `LOWER`, `NUMERIC` and `SPECIAL` constants and a `build`
|
|
55
|
+
method that returns the pool of characters for the selected sets.
|
|
56
|
+
- **`PasswordForge::Validation`** — a module with `validate_charset_selection`
|
|
57
|
+
(raises when no set is selected) and `validate_length` (positive integer).
|
|
58
|
+
- **`PasswordForge::NoCharsetSelectedError`** — the equivalent of the C#
|
|
59
|
+
`InvalidCharSetException`; subclass of `PasswordForge::Error`.
|
|
60
|
+
- **`PasswordForge::Generator`** — the public class. The constructor validates
|
|
61
|
+
input and builds the pool; `#generate` returns a `SecureRandom`-backed
|
|
62
|
+
password.
|
|
63
|
+
- **`PasswordForge.generate(**options)`** — a top-level convenience wrapper.
|
|
64
|
+
|
|
65
|
+
## Public API conventions
|
|
66
|
+
|
|
67
|
+
- The `Generator` constructor uses keyword arguments:
|
|
68
|
+
`upper_case:`, `lower_case:`, `numeric_case:`, `special_case:` (all `true`),
|
|
69
|
+
and `length:` (default 16). This matches the C# constructor parameter names.
|
|
70
|
+
- A fluent/builder API is planned for v0.6.0 and must be **additive**: the
|
|
71
|
+
keyword-argument API keeps working unchanged.
|
|
72
|
+
|
|
73
|
+
## Naming
|
|
74
|
+
|
|
75
|
+
- **Gem name:** `password_forge` (underscore) — what users `gem install`.
|
|
76
|
+
- **GitHub repo:** `password-forge-ruby-gem` (hyphens) — more descriptive for
|
|
77
|
+
discovery. The two intentionally differ.
|
|
78
|
+
- Namespace all code under the `PasswordForge` module; one file per component
|
|
79
|
+
under `lib/password_forge/`.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Tech
|
|
2
|
+
|
|
3
|
+
## Stack
|
|
4
|
+
|
|
5
|
+
- **Language:** Ruby, `required_ruby_version >= 3.0.0`.
|
|
6
|
+
- **Test framework:** RSpec.
|
|
7
|
+
- **Linter:** RuboCop (config in `.rubocop.yml`, `TargetRubyVersion: 3.0`).
|
|
8
|
+
- **Randomness:** `SecureRandom` (standard library, no runtime dependencies).
|
|
9
|
+
- **Type signatures:** RBS stubs under `sig/`.
|
|
10
|
+
|
|
11
|
+
The gem has **no runtime dependencies**.
|
|
12
|
+
|
|
13
|
+
## Common commands
|
|
14
|
+
|
|
15
|
+
Run from the repository root (`password-forge-ruby-gem/`):
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bundle install # install development dependencies
|
|
19
|
+
bundle exec rake # default task: RSpec + RuboCop
|
|
20
|
+
bundle exec rspec # run the test suite only
|
|
21
|
+
bundle exec rubocop # run the linter only
|
|
22
|
+
bundle exec rubocop -A # auto-correct safe offences
|
|
23
|
+
gem build password_forge.gemspec # build the gem locally
|
|
24
|
+
bin/console # interactive prompt with the gem loaded
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Development workflow
|
|
28
|
+
|
|
29
|
+
- **TDD.** Write the spec first, watch it fail, implement to green, then
|
|
30
|
+
refactor. Keep the suite and RuboCop green before every commit.
|
|
31
|
+
- **Branches.** Do feature work on a `feature/vX.Y.Z-*` branch and merge to
|
|
32
|
+
`main` per milestone. Never push directly to `main` for feature work.
|
|
33
|
+
- **Versioning.** Semantic Versioning. Bump `lib/password_forge/version.rb`,
|
|
34
|
+
update `CHANGELOG.md` (Keep a Changelog format), then tag `vX.Y.Z`.
|
|
35
|
+
|
|
36
|
+
## Release process (Trusted Publishing)
|
|
37
|
+
|
|
38
|
+
Publishing is automated via OIDC — no API tokens are stored.
|
|
39
|
+
|
|
40
|
+
1. A one-time setup on RubyGems.org registers a trusted publisher:
|
|
41
|
+
- RubyGem name: `password_forge`
|
|
42
|
+
- Repository owner: `devandreacarratta`
|
|
43
|
+
- Repository name: `password-forge-ruby-gem`
|
|
44
|
+
- Workflow filename: `release.yml`
|
|
45
|
+
- Environment: `release`
|
|
46
|
+
For the very first publish (gem not yet on RubyGems), a **pending** trusted
|
|
47
|
+
publisher is registered from the RubyGems profile before the gem exists.
|
|
48
|
+
2. `.github/workflows/release.yml` triggers on `v*` tags, uses
|
|
49
|
+
`rubygems/release-gem@v1` with `contents: write` + `id-token: write` and the
|
|
50
|
+
`release` GitHub environment.
|
|
51
|
+
3. Releasing = bump version, update CHANGELOG, merge to `main`, push the tag.
|
|
52
|
+
|
|
53
|
+
## Key decisions (recorded so they are not re-litigated)
|
|
54
|
+
|
|
55
|
+
- **Gem email removed.** `spec.email` is intentionally omitted from the gemspec
|
|
56
|
+
(it is optional and would be public). The RubyGems account email is separate
|
|
57
|
+
and private.
|
|
58
|
+
- **`Gemfile.lock` is not committed.** Committing it pinned `BUNDLED WITH 4.x`,
|
|
59
|
+
which broke CI on Ruby < 3.2 (Bundler 4 requires Ruby 3.2+). Gems resolve the
|
|
60
|
+
lockfile per environment, so it is git-ignored.
|
|
61
|
+
- **Gem name vs repo name differ on purpose** (`password_forge` vs
|
|
62
|
+
`password-forge-ruby-gem`).
|
|
63
|
+
- **MFA required for pushes** via `rubygems_mfa_required = "true"` in the
|
|
64
|
+
gemspec.
|
|
65
|
+
- **CI splits test and lint:** RSpec runs across Ruby 3.0–3.4; RuboCop runs once
|
|
66
|
+
on 3.4 to avoid version-specific style noise.
|
|
67
|
+
- **Personal diary** lives in `private-notes/` (git-ignored), everything else is
|
|
68
|
+
English and tracked.
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.0.2] - 2026-09-05
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `examples/` folder with runnable local demos: `smoke_test.rb` (loads the gem
|
|
15
|
+
from source and prints sample passwords) and `local-consumer/` (a minimal
|
|
16
|
+
external project that depends on the gem via a `path:` reference).
|
|
17
|
+
- `docs/` folder with a documentation index (`docs/README.md`) and a
|
|
18
|
+
local-testing guide (`docs/local-testing.md`).
|
|
19
|
+
- "Trying it locally" section in the README linking to the examples and docs.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Excluded `examples/` and `docs/` from the packaged gem.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- Corrected the directory tree and command paths in the Kiro steering docs
|
|
28
|
+
(`structure.md`, `tech.md`), which still referenced a nested `password_forge/`
|
|
29
|
+
root left over from an older project layout.
|
|
30
|
+
|
|
31
|
+
## [0.0.1] - 2026-09-05
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- `PasswordForge::Generator` with keyword-argument constructor
|
|
36
|
+
(`upper_case:`, `lower_case:`, `numeric_case:`, `special_case:`, `length:`)
|
|
37
|
+
and a `SecureRandom`-backed `#generate`.
|
|
38
|
+
- `PasswordForge::Charset` value object exposing the `UPPER`, `LOWER`,
|
|
39
|
+
`NUMERIC` and `SPECIAL` character sets and a `build` method.
|
|
40
|
+
- `PasswordForge::Validation` for charset selection and length checks.
|
|
41
|
+
- `PasswordForge::NoCharsetSelectedError`, raised when no character set is
|
|
42
|
+
selected.
|
|
43
|
+
- `PasswordForge.generate` convenience wrapper.
|
|
44
|
+
- RSpec test suite, RuboCop configuration and README.
|
|
45
|
+
|
|
46
|
+
[Unreleased]: https://github.com/devandreacarratta/password-forge-ruby-gem/compare/v0.0.2...HEAD
|
|
47
|
+
[0.0.2]: https://github.com/devandreacarratta/password-forge-ruby-gem/compare/v0.0.1...v0.0.2
|
|
48
|
+
[0.0.1]: https://github.com/devandreacarratta/password-forge-ruby-gem/releases/tag/v0.0.1
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andrea Carratta
|
|
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,152 @@
|
|
|
1
|
+
# PasswordForge
|
|
2
|
+
|
|
3
|
+
[](https://github.com/devandreacarratta/password-forge-ruby-gem/actions/workflows/main.yml)
|
|
4
|
+
|
|
5
|
+
A configurable password generator for Ruby with selectable character sets.
|
|
6
|
+
|
|
7
|
+
`PasswordForge` builds passwords from four character categories — uppercase,
|
|
8
|
+
lowercase, numeric and special — that you switch on or off independently. All
|
|
9
|
+
categories are enabled by default, and a clear error is raised if you disable
|
|
10
|
+
every one of them. Randomness is provided by Ruby's `SecureRandom`.
|
|
11
|
+
|
|
12
|
+
This gem is also a showcase for building and shipping a Ruby gem the **Kiro
|
|
13
|
+
way**. Kiro skills, project steering, hooks and an MCP server are being added
|
|
14
|
+
incrementally (see the [Roadmap](#roadmap)).
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
> **Not on RubyGems yet.** The first public release is planned for 0.1.0 (see
|
|
19
|
+
> the [Roadmap](#roadmap)). Until then, see [Trying it
|
|
20
|
+
> locally](#trying-it-locally) to run the gem from source or via a local path
|
|
21
|
+
> dependency.
|
|
22
|
+
|
|
23
|
+
Once published, install the gem and add it to the application's Gemfile by
|
|
24
|
+
executing:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
bundle add password_forge
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
If Bundler is not being used to manage dependencies, install the gem by
|
|
31
|
+
executing:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
gem install password_forge
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
### Quick start
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
require "password_forge"
|
|
43
|
+
|
|
44
|
+
# All character sets enabled, default length of 16
|
|
45
|
+
PasswordForge.generate
|
|
46
|
+
# => "aB3$xY7!qR2@kL9%"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Using the generator directly
|
|
50
|
+
|
|
51
|
+
The constructor takes four boolean flags (all `true` by default) plus a
|
|
52
|
+
`length`. This mirrors the design of the original C# / NuGet package:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
generator = PasswordForge::Generator.new(
|
|
56
|
+
upper_case: true, # include A-Z
|
|
57
|
+
lower_case: true, # include a-z
|
|
58
|
+
numeric_case: true, # include 0-9
|
|
59
|
+
special_case: true, # include special characters
|
|
60
|
+
length: 16
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
generator.generate # => "aB3$xY7!qR2@kL9%"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Examples
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
# A 20-character password using every character set
|
|
70
|
+
PasswordForge::Generator.new(length: 20).generate
|
|
71
|
+
|
|
72
|
+
# A 4-digit numeric PIN
|
|
73
|
+
PasswordForge::Generator.new(
|
|
74
|
+
upper_case: false, lower_case: false, numeric_case: true, special_case: false, length: 4
|
|
75
|
+
).generate
|
|
76
|
+
# => "8391"
|
|
77
|
+
|
|
78
|
+
# Letters only (no digits, no symbols)
|
|
79
|
+
PasswordForge::Generator.new(
|
|
80
|
+
numeric_case: false, special_case: false, length: 24
|
|
81
|
+
).generate
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Error handling
|
|
85
|
+
|
|
86
|
+
Disabling every character set raises `PasswordForge::NoCharsetSelectedError`:
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
PasswordForge::Generator.new(
|
|
90
|
+
upper_case: false, lower_case: false, numeric_case: false, special_case: false
|
|
91
|
+
)
|
|
92
|
+
# => raises PasswordForge::NoCharsetSelectedError
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
A non-positive or non-integer `length` raises `ArgumentError`.
|
|
96
|
+
|
|
97
|
+
## Character sets
|
|
98
|
+
|
|
99
|
+
| Flag | Characters |
|
|
100
|
+
| -------------- | ------------------------ |
|
|
101
|
+
| `upper_case` | `A`–`Z` |
|
|
102
|
+
| `lower_case` | `a`–`z` |
|
|
103
|
+
| `numeric_case` | `0`–`9` |
|
|
104
|
+
| `special_case` | `` !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ `` |
|
|
105
|
+
|
|
106
|
+
## Trying it locally
|
|
107
|
+
|
|
108
|
+
You can run the gem without installing it from RubyGems:
|
|
109
|
+
|
|
110
|
+
- **From this repository:** `ruby examples/smoke_test.rb` prints a few sample
|
|
111
|
+
passwords straight from the source tree.
|
|
112
|
+
- **From another project:** add `gem "password_forge", path:
|
|
113
|
+
"/path/to/password-forge-ruby-gem"` to that project's `Gemfile`, run `bundle
|
|
114
|
+
install`, then `require "password_forge"`. See
|
|
115
|
+
[`examples/local-consumer/`](examples/local-consumer) for a working example.
|
|
116
|
+
|
|
117
|
+
Full instructions, including the interactive console and building a local
|
|
118
|
+
`.gem`, are in [docs/local-testing.md](docs/local-testing.md).
|
|
119
|
+
|
|
120
|
+
## Roadmap
|
|
121
|
+
|
|
122
|
+
`PasswordForge` is developed in incremental, tagged releases:
|
|
123
|
+
|
|
124
|
+
- **0.0.1** — Core generator, character sets, validation, tests, docs.
|
|
125
|
+
- **0.0.2** — Local-testing examples (`examples/`) and documentation (`docs/`).
|
|
126
|
+
- **0.1.0** — First public release on RubyGems.org via Trusted Publishing.
|
|
127
|
+
- **0.2.0** — Kiro skills for gem authors (feature TDD, version bump, release).
|
|
128
|
+
- **0.3.0** — Project `.kiro/` folder with steering and conventions.
|
|
129
|
+
- **0.4.0** — Kiro hooks (run specs on save, changelog reminders, and more).
|
|
130
|
+
- **0.5.0** — A Ruby MCP server exposing password generation as a tool.
|
|
131
|
+
- **0.6.0** — A fluent/builder API on top of the keyword-argument API.
|
|
132
|
+
|
|
133
|
+
## Development
|
|
134
|
+
|
|
135
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then run
|
|
136
|
+
`bundle exec rake` to run the tests and the linter. You can also run
|
|
137
|
+
`bin/console` for an interactive prompt to experiment, or
|
|
138
|
+
`ruby examples/smoke_test.rb` for a quick check straight from source.
|
|
139
|
+
|
|
140
|
+
To install this gem onto your local machine, run `bundle exec rake install`. For
|
|
141
|
+
the full local workflow — including running it from a separate project and
|
|
142
|
+
uninstalling — see [docs/local-testing.md](docs/local-testing.md).
|
|
143
|
+
|
|
144
|
+
## Contributing
|
|
145
|
+
|
|
146
|
+
Bug reports and pull requests are welcome on GitHub at
|
|
147
|
+
<https://github.com/devandreacarratta/password-forge-ruby-gem>.
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
The gem is available as open source under the terms of the
|
|
152
|
+
[MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PasswordForge
|
|
4
|
+
# Value object holding the available character sets and building the pool
|
|
5
|
+
# of characters from the selected sets.
|
|
6
|
+
#
|
|
7
|
+
# The four sets mirror the character categories of the original C# design:
|
|
8
|
+
# uppercase (A-Z), lowercase (a-z), numeric (0-9) and special (punctuation).
|
|
9
|
+
module Charset
|
|
10
|
+
# Uppercase letters, from 'A' to 'Z'.
|
|
11
|
+
UPPER = ("A".."Z").to_a.freeze
|
|
12
|
+
|
|
13
|
+
# Lowercase letters, from 'a' to 'z'.
|
|
14
|
+
LOWER = ("a".."z").to_a.freeze
|
|
15
|
+
|
|
16
|
+
# Numeric digits, from '0' to '9'.
|
|
17
|
+
NUMERIC = ("0".."9").to_a.freeze
|
|
18
|
+
|
|
19
|
+
# Special (punctuation) characters.
|
|
20
|
+
SPECIAL = %w[! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \\ ] ^ _ ` { | } ~].freeze
|
|
21
|
+
|
|
22
|
+
# Builds the pool of characters from the selected sets.
|
|
23
|
+
#
|
|
24
|
+
# @param upper [Boolean] include uppercase letters (A-Z)
|
|
25
|
+
# @param lower [Boolean] include lowercase letters (a-z)
|
|
26
|
+
# @param numeric [Boolean] include numeric digits (0-9)
|
|
27
|
+
# @param special [Boolean] include special characters
|
|
28
|
+
# @return [Array<String>] the combined pool of unique characters
|
|
29
|
+
def self.build(upper:, lower:, numeric:, special:)
|
|
30
|
+
pool = []
|
|
31
|
+
pool.concat(UPPER) if upper
|
|
32
|
+
pool.concat(LOWER) if lower
|
|
33
|
+
pool.concat(NUMERIC) if numeric
|
|
34
|
+
pool.concat(SPECIAL) if special
|
|
35
|
+
pool
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PasswordForge
|
|
4
|
+
# Base error class for all PasswordForge-specific errors.
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
|
|
7
|
+
# Raised when a generator is configured with no character set selected,
|
|
8
|
+
# mirroring the +InvalidCharSetException+ of the original C# design.
|
|
9
|
+
class NoCharsetSelectedError < Error
|
|
10
|
+
# Default message shown when no character set is selected.
|
|
11
|
+
DEFAULT_MESSAGE = "At least one character set must be selected " \
|
|
12
|
+
"(upper_case, lower_case, numeric_case or special_case)."
|
|
13
|
+
|
|
14
|
+
def initialize(message = DEFAULT_MESSAGE)
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module PasswordForge
|
|
6
|
+
# Generates random passwords from selectable character sets.
|
|
7
|
+
#
|
|
8
|
+
# The constructor mirrors the original C# +PasswordGenerator+ design: four
|
|
9
|
+
# boolean flags select the character sets, all enabled by default, and a
|
|
10
|
+
# {NoCharsetSelectedError} is raised when none is selected.
|
|
11
|
+
#
|
|
12
|
+
# @example Generate a password with all character sets (default)
|
|
13
|
+
# PasswordForge::Generator.new.generate # => "aB3$xY7!qR2@kL9%"
|
|
14
|
+
#
|
|
15
|
+
# @example Generate a numeric-only PIN
|
|
16
|
+
# PasswordForge::Generator.new(
|
|
17
|
+
# upper_case: false, lower_case: false, numeric_case: true, special_case: false, length: 4
|
|
18
|
+
# ).generate # => "8391"
|
|
19
|
+
class Generator
|
|
20
|
+
# Default password length used when none is specified.
|
|
21
|
+
DEFAULT_LENGTH = 16
|
|
22
|
+
|
|
23
|
+
# @return [Integer] the configured password length
|
|
24
|
+
attr_reader :length
|
|
25
|
+
|
|
26
|
+
# @param upper_case [Boolean] include uppercase letters (A-Z)
|
|
27
|
+
# @param lower_case [Boolean] include lowercase letters (a-z)
|
|
28
|
+
# @param numeric_case [Boolean] include numeric digits (0-9)
|
|
29
|
+
# @param special_case [Boolean] include special characters
|
|
30
|
+
# @param length [Integer] the length of the generated password
|
|
31
|
+
# @raise [NoCharsetSelectedError] if no character set is selected
|
|
32
|
+
# @raise [ArgumentError] if length is not a positive integer
|
|
33
|
+
def initialize(upper_case: true, lower_case: true, numeric_case: true, special_case: true,
|
|
34
|
+
length: DEFAULT_LENGTH)
|
|
35
|
+
Validation.validate_charset_selection(
|
|
36
|
+
upper: upper_case, lower: lower_case, numeric: numeric_case, special: special_case
|
|
37
|
+
)
|
|
38
|
+
Validation.validate_length(length)
|
|
39
|
+
|
|
40
|
+
@length = length
|
|
41
|
+
@pool = Charset.build(
|
|
42
|
+
upper: upper_case, lower: lower_case, numeric: numeric_case, special: special_case
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Generates a random password of the configured length.
|
|
47
|
+
#
|
|
48
|
+
# Uses {SecureRandom} for cryptographically secure randomness.
|
|
49
|
+
#
|
|
50
|
+
# @return [String] the generated password
|
|
51
|
+
def generate
|
|
52
|
+
Array.new(@length) { @pool[SecureRandom.random_number(@pool.length)] }.join
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Convenience wrapper around {Generator#generate}.
|
|
57
|
+
#
|
|
58
|
+
# @see Generator#initialize
|
|
59
|
+
# @return [String] the generated password
|
|
60
|
+
def self.generate(**options)
|
|
61
|
+
Generator.new(**options).generate
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PasswordForge
|
|
4
|
+
# Validates the configuration of a password generator.
|
|
5
|
+
#
|
|
6
|
+
# Mirrors the validation responsibilities of the original C# design:
|
|
7
|
+
# rejecting a configuration with no character set selected and rejecting
|
|
8
|
+
# an invalid password length.
|
|
9
|
+
module Validation
|
|
10
|
+
# Ensures that at least one character set is selected.
|
|
11
|
+
#
|
|
12
|
+
# @param upper [Boolean] uppercase flag
|
|
13
|
+
# @param lower [Boolean] lowercase flag
|
|
14
|
+
# @param numeric [Boolean] numeric flag
|
|
15
|
+
# @param special [Boolean] special flag
|
|
16
|
+
# @raise [NoCharsetSelectedError] if every flag is false
|
|
17
|
+
# @return [void]
|
|
18
|
+
def self.validate_charset_selection(upper:, lower:, numeric:, special:)
|
|
19
|
+
return if upper || lower || numeric || special
|
|
20
|
+
|
|
21
|
+
raise NoCharsetSelectedError
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Ensures that the requested password length is a positive integer.
|
|
25
|
+
#
|
|
26
|
+
# @param length [Integer] the requested password length
|
|
27
|
+
# @raise [ArgumentError] if length is not a positive integer
|
|
28
|
+
# @return [void]
|
|
29
|
+
def self.validate_length(length)
|
|
30
|
+
raise ArgumentError, "length must be an Integer, got #{length.class}" unless length.is_a?(Integer)
|
|
31
|
+
raise ArgumentError, "length must be a positive integer, got #{length}" unless length.positive?
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "password_forge/version"
|
|
4
|
+
require_relative "password_forge/errors"
|
|
5
|
+
require_relative "password_forge/charset"
|
|
6
|
+
require_relative "password_forge/validation"
|
|
7
|
+
require_relative "password_forge/generator"
|
|
8
|
+
|
|
9
|
+
# PasswordForge generates random passwords from selectable character sets.
|
|
10
|
+
module PasswordForge
|
|
11
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: password_forge
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.2
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Andrea Carratta
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: PasswordForge generates random passwords from selectable character sets
|
|
13
|
+
(uppercase, lowercase, numeric, special). Built as a learning-friendly, well-tested
|
|
14
|
+
Ruby gem with a clean, idiomatic API.
|
|
15
|
+
executables: []
|
|
16
|
+
extensions: []
|
|
17
|
+
extra_rdoc_files: []
|
|
18
|
+
files:
|
|
19
|
+
- ".kiro/steering/product.md"
|
|
20
|
+
- ".kiro/steering/structure.md"
|
|
21
|
+
- ".kiro/steering/tech.md"
|
|
22
|
+
- CHANGELOG.md
|
|
23
|
+
- LICENSE.txt
|
|
24
|
+
- README.md
|
|
25
|
+
- Rakefile
|
|
26
|
+
- lib/password_forge.rb
|
|
27
|
+
- lib/password_forge/charset.rb
|
|
28
|
+
- lib/password_forge/errors.rb
|
|
29
|
+
- lib/password_forge/generator.rb
|
|
30
|
+
- lib/password_forge/validation.rb
|
|
31
|
+
- lib/password_forge/version.rb
|
|
32
|
+
- sig/password_forge.rbs
|
|
33
|
+
homepage: https://github.com/devandreacarratta/password-forge-ruby-gem
|
|
34
|
+
licenses:
|
|
35
|
+
- MIT
|
|
36
|
+
metadata:
|
|
37
|
+
homepage_uri: https://github.com/devandreacarratta/password-forge-ruby-gem
|
|
38
|
+
source_code_uri: https://github.com/devandreacarratta/password-forge-ruby-gem/tree/main
|
|
39
|
+
changelog_uri: https://github.com/devandreacarratta/password-forge-ruby-gem/blob/main/CHANGELOG.md
|
|
40
|
+
rubygems_mfa_required: 'true'
|
|
41
|
+
rdoc_options: []
|
|
42
|
+
require_paths:
|
|
43
|
+
- lib
|
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: 3.0.0
|
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
requirements: []
|
|
55
|
+
rubygems_version: 4.0.16
|
|
56
|
+
specification_version: 4
|
|
57
|
+
summary: A configurable password generator with selectable character sets.
|
|
58
|
+
test_files: []
|