Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

42 Improve Token Security & Extend wac.install #50

Merged
merged 115 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
115 commits
Select commit Hold shift + click to select a range
8ef8c72
convert modules to esm
type1fool Sep 10, 2023
36c8431
bump version, fix missing task docs
type1fool Sep 11, 2023
487ebe2
add shortdoc
type1fool Sep 11, 2023
c76aada
exclude generators from docs
type1fool Sep 11, 2023
48d59be
ignore macos index files
type1fool Sep 11, 2023
1eeee10
raise when `@app` is missing
type1fool Sep 11, 2023
d5950f5
wip
type1fool Sep 11, 2023
220b54d
document options and templates
type1fool Sep 11, 2023
cd70c09
clean up defaults
type1fool Sep 11, 2023
be9ea4d
fix app name in auth html template
type1fool Sep 11, 2023
c5e597a
fix auth live view template file names
type1fool Sep 11, 2023
f1460c2
skip and print router for now
type1fool Sep 11, 2023
30c838a
add link to slack convo
type1fool Sep 11, 2023
23f6e7a
add sourceror, sort deps
type1fool Sep 12, 2023
cc37177
wip router logic
type1fool Sep 12, 2023
7ad8bcd
require `app.start`
type1fool Sep 12, 2023
5e6a179
functional router modifications
type1fool Sep 12, 2023
b222e34
extract functions, improve error handling
type1fool Sep 13, 2023
7234507
add success message
type1fool Sep 13, 2023
d5f9512
rename `users` to `identity`
type1fool Sep 13, 2023
da4f34c
add notes about ULID
type1fool Sep 13, 2023
cc7df3e
add `templates/` to list of files
type1fool Sep 13, 2023
1c11151
rm token test
type1fool Sep 13, 2023
291c907
update tests, fix app assign check
type1fool Sep 13, 2023
fee9560
ci: update cache config
type1fool Sep 13, 2023
cea1b55
accept assigns for text and icon
type1fool Sep 14, 2023
163594d
add req
type1fool Sep 14, 2023
5137bcc
set cookie via req instead of hacky form,
type1fool Sep 14, 2023
a2033ff
add prelim live view test
type1fool Sep 14, 2023
549ad8c
update fieldset styles
type1fool Sep 14, 2023
b198f17
generate token in changeset
type1fool Sep 14, 2023
ebd3ec5
fix token generation
type1fool Sep 14, 2023
2d5429c
fix session request
type1fool Sep 14, 2023
ddb162c
add csrf token to session request
type1fool Sep 14, 2023
a70a10d
use socket host uri for session request
type1fool Sep 14, 2023
bbd727e
fix csrf pattern match
type1fool Sep 14, 2023
0d0a426
assign csrf to socket since it's not avail post-mount
type1fool Sep 14, 2023
133cb39
fix csrf token fetch
type1fool Sep 14, 2023
cb17882
rm csrf from auth live
type1fool Sep 14, 2023
21c89ff
return responses from session
type1fool Sep 14, 2023
78714f6
actually send the response
type1fool Sep 14, 2023
44d78bc
fix session req uri path
type1fool Sep 14, 2023
fda7335
rm req for now
type1fool Sep 14, 2023
99fb6e6
revert session controller changes
type1fool Sep 14, 2023
966ecb7
revert back to token form
type1fool Sep 14, 2023
6ee4cae
rm page title due to eex confusion
type1fool Sep 14, 2023
42bf93f
inject js hooks
type1fool Sep 14, 2023
8810e29
update regex, add status message
type1fool Sep 14, 2023
38b734d
handle missing spaces
type1fool Sep 14, 2023
a778007
update capture replacement
type1fool Sep 14, 2023
5b3b271
fix copy error
type1fool Sep 14, 2023
64b5c69
add missing assign
type1fool Sep 14, 2023
d2da2a0
add test placeholders
type1fool Sep 14, 2023
ce2e606
fix token query, move expiration to module attr for visibility
type1fool Sep 14, 2023
d648e2c
remove stray comment
type1fool Sep 14, 2023
36e0d03
add navigation components
type1fool Sep 15, 2023
e8f3833
extract generator error
type1fool Sep 15, 2023
6683749
add component and app_html generators
type1fool Sep 15, 2023
523a1f4
move heex templates into subdir
type1fool Sep 15, 2023
6000052
don't format heex files
type1fool Sep 15, 2023
b8b4227
reorder web generators
type1fool Sep 15, 2023
9316f93
inspect the module name
type1fool Sep 15, 2023
180f555
add missing module name
type1fool Sep 15, 2023
5dd9131
derp
type1fool Sep 15, 2023
dc2a840
add `embed_templates`
type1fool Sep 15, 2023
094fd2a
wrap template strings
type1fool Sep 15, 2023
3e27296
use `:if` attr
type1fool Sep 15, 2023
6c200b6
wrap render slot in unescaped string
type1fool Sep 15, 2023
3181203
use `copy_file` for nav templates
type1fool Sep 15, 2023
01deb18
use verified routes
type1fool Sep 15, 2023
e264f0b
adjust navbar styles
type1fool Sep 15, 2023
d2c85a1
adjust navbar appearance
type1fool Sep 15, 2023
ac35078
inject sign-in link to root
type1fool Sep 15, 2023
fc77e6d
correct the page file path
type1fool Sep 15, 2023
150a518
add page html injector
type1fool Sep 15, 2023
9486007
fix token query
type1fool Sep 15, 2023
8c982d5
inspect the app atom
type1fool Sep 15, 2023
e52be28
add `UserTokenCleaner` to automatically clean expired tokens
type1fool Sep 16, 2023
e3f659d
rm io inspect
type1fool Sep 16, 2023
2cd4abf
rm errant alias
type1fool Sep 16, 2023
e39b958
fix `UserTokenCleaner`
type1fool Sep 16, 2023
01273ec
fix timer ref
type1fool Sep 16, 2023
e5e2daa
add `delete_token/1`, add docs to `delete_all_expired_tokens/0`
type1fool Sep 16, 2023
cf05641
add `@attestation` attr, sort attrs
type1fool Sep 16, 2023
02eb5de
cleaner: use regular messages
type1fool Sep 17, 2023
6e657de
enable changeset error rendering in the form
type1fool Sep 17, 2023
e07a1a7
Revert "add `@attestation` attr, sort attrs"
type1fool Sep 17, 2023
30a3385
wip: update test and fixture templates
type1fool Sep 17, 2023
c92272c
add dialyxir
type1fool Sep 17, 2023
a61cb09
authenticate user key found in repo matches authenticator
type1fool Sep 17, 2023
e95f98a
add fixtures
type1fool Sep 17, 2023
2adf555
test `registration_successful` message
type1fool Sep 17, 2023
f449d9b
fix alias
type1fool Sep 17, 2023
fd2e742
cleanup comments
type1fool Sep 17, 2023
e55ea5c
add basic guidance about passkeys
type1fool Sep 17, 2023
3eb4042
auth_component: improve error handling
type1fool Sep 17, 2023
f45953f
add attestation attrs
type1fool Sep 17, 2023
c843dab
update guidance summary appearance
type1fool Sep 17, 2023
9fc2b5f
add invalid attestation test
type1fool Sep 17, 2023
c458cb7
update description
type1fool Sep 17, 2023
929cf7c
update readme, rm usage doc for now
type1fool Sep 17, 2023
c21ff4a
reorganize the readme, extract webauthn flows to new file
type1fool Sep 17, 2023
007bca9
wip: webauthn flow updates
type1fool Sep 17, 2023
9d763cb
add `page_controller.ex`, update `app.html.ex`
type1fool Sep 17, 2023
3ef2814
add flash messages to `session.ex`
type1fool Sep 17, 2023
36b84e4
adjust default flash placement
type1fool Sep 17, 2023
077a1d4
update flow diagrams
type1fool Sep 17, 2023
d0cede5
cleanup
type1fool Sep 17, 2023
5ec40f0
update documentation
type1fool Sep 17, 2023
c74be72
fix typespecs
type1fool Sep 17, 2023
7da6df9
add `webauthn_flows.md` to doc extras
type1fool Sep 17, 2023
c4b2da7
fix flaky test
type1fool Sep 18, 2023
145ff87
update readme
type1fool Sep 18, 2023
8f63186
add toc to webauthn flows
type1fool Sep 18, 2023
bf2eb95
fix toc in webauthn flows
type1fool Sep 18, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .formatter.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
[
plugins: [Phoenix.LiveView.HTMLFormatter],
import_deps: [:ecto, :phoenix, :phoenix_live_view],
inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"]
inputs: ["*.{heex,ex,exs}", "{config,lib,templates,test}/**/*.{heex,ex,exs}"]
]
7 changes: 5 additions & 2 deletions .github/workflows/elixir.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,18 @@ jobs:
with:
path: deps
key: ${{ runner.os }}-${{ matrix.elixir }}-mix-${{ hashFiles('**/mix.lock') }}
restore-keys: ${{ runner.os }}-${{ matrix.elixir }}-mix-
restore-keys: |
${{ runner.os }}-${{ matrix.elixir }}-mix-${{ hashFiles('**/mix.lock') }}
${{ runner.os }}-${{ matrix.elixir }}-mix-
${{ runner.os }}-

- name: Install dependencies
run: mix deps.get

- name: Compilation Cache
uses: actions/cache@v3
with:
path: _build/test
path: _build
key: ${{ runner.os }}-${{ matrix.elixir }}-compiled-${{ hashfiles('./mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ matrix.elixir }}-compiled-${{ hashfiles('./mix.lock') }}
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# MacOS index files
.DS_Store

# The directory Mix will write compiled artifacts to.
/_build/
/.elixir_ls
Expand Down
297 changes: 124 additions & 173 deletions README.md

Large diffs are not rendered by default.

106 changes: 0 additions & 106 deletions USAGE.md

This file was deleted.

104 changes: 94 additions & 10 deletions lib/mix/tasks/wac.install.ex
Original file line number Diff line number Diff line change
@@ -1,20 +1,61 @@
defmodule Mix.Tasks.Wac.Install do
@moduledoc """
Generates schemas, migrations, and contexts for users with WebauthnComponents as the primary authentication mechanism.
"""
@shortdoc "Generates a user schema."

## Options

By default, contexts, schemas, tests, and web modules are generated or modified by `wac.install`. You may opt out of one or more generators with the following options:

- `--no-contexts`: Do not generate context modules.
- `--no-schemas`: Do not generate schema & migration modules.
- `--no-tests`: Do not generate test modules & scripts.
- `--no-web`: Do not generate the authentication LiveView, the session controller, or session hooks.
- `--no-router`: Do not modify the router.

## Templates

Within the [`webauthn_components`](https://github.com/liveshowy/webauthn_components) repo, the following templates are used by `wac.install` to scaffold the modules needed to support Passkeys in a LiveView application:

#{for dir <- File.ls!("templates") |> Enum.sort(),
file <- Path.join(["templates", dir]) |> File.ls!() |> Enum.sort() do
"\n- `templates/#{dir}/#{file}`"
end}
"""
use Mix.Task
alias Wac.Gen.Contexts
alias Wac.Gen.Controllers
alias Wac.Gen.Schemas
alias Wac.Gen.LiveViews
alias Wac.Gen.SessionHooks
alias Wac.Gen.Migrations
alias Wac.Gen.Router
alias Wac.Gen.Tests
alias Wac.Gen.Fixtures
alias Wac.Gen.Javascript
alias Wac.Gen.Components
alias Wac.Gen.AppHtml
alias Wac.Gen.Misc

@version Mix.Project.config()[:version]
@shortdoc "Generates a user schema."

@mix_task "wac.install"
@switches []
@switches [
contexts: :boolean,
schemas: :boolean,
tests: :boolean,
web: :boolean,
router: :boolean
]
@default_opts [
contexts: true,
schemas: true,
tests: true,
web: true,
router: true
]

@requirements ["app.start"]

@impl Mix.Task
def run([version]) when version in ~w(-v --version) do
Expand All @@ -31,20 +72,63 @@ defmodule Mix.Tasks.Wac.Install do
end

case OptionParser.parse(args, strict: @switches) do
{_parsed, _args, []} ->
{flags, _args, []} ->
opts = Keyword.merge(@default_opts, flags)
dirname = File.cwd!() |> Path.basename()
dirname_camelized = Macro.camelize(dirname)
web_dirname = dirname <> "_web"
web_dirname_camelized = Macro.camelize(web_dirname)

assigns = [
app_snake_case: dirname,
app_pascal_case: Module.concat([dirname_camelized])
app_pascal_case: Module.concat([dirname_camelized]),
web_snake_case: web_dirname,
web_pascal_case: Module.concat([web_dirname_camelized])
]

Schemas.copy_templates(assigns)
Migrations.copy_templates(assigns)
Contexts.copy_templates(assigns)
Tests.copy_templates(assigns)
Fixtures.copy_templates(assigns)
if opts[:contexts] do
Contexts.copy_templates(assigns)
Misc.copy_templates(assigns)
end

if opts[:schemas] do
Schemas.copy_templates(assigns)
Migrations.copy_templates(assigns)
end

if opts[:tests] do
Tests.copy_templates(assigns)
Fixtures.copy_templates(assigns)
end

if opts[:web] do
Controllers.copy_templates(assigns)
LiveViews.copy_templates(assigns)
AppHtml.update_app_html(assigns)
AppHtml.update_page_html(assigns)
SessionHooks.copy_templates(assigns)
Components.copy_templates(assigns)

Javascript.inject_hooks()
end

if opts[:router] do
Router.update_router(assigns)
end

success_message =
"""

✅ Successfully scaffolded WebauthnComponents for #{inspect(assigns[:app_pascal_case])}

📚 Resources

- Repo:\thttps://github.com/liveshowy/webauthn_components
- Hex:\thttps://hex.pm/packages/webauthn_components
- Docs:\thttps://hexdocs.pm/webauthn_components/readme.html
"""

IO.puts(success_message)

{_parsed, _args, errors} ->
invalid_opts =
Expand Down
49 changes: 49 additions & 0 deletions lib/wac_gen/app_html.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
defmodule Wac.Gen.AppHtml do
@moduledoc false

@header_regex ~r/\<header.*\<\/header\>/mis

def update_app_html(assigns) do
web_snake_case = Keyword.fetch!(assigns, :web_snake_case)
file_path = Path.join(["lib", web_snake_case, "components", "layouts", "app.html.heex"])

modified_file_contents =
file_path
|> File.read!()
|> String.replace(@header_regex, navbar_component(assigns))

File.write!(file_path, modified_file_contents)
end

defp navbar_component(assigns) do
web_pascal_case = Keyword.fetch!(assigns, :web_pascal_case)

"""
<#{inspect(web_pascal_case)}.NavigationComponents.navbar current_user={@current_user} />
"""
end

@flash_string "<.flash_group flash={@flash} />"

def update_page_html(assigns) do
web_snake_case = Keyword.fetch!(assigns, :web_snake_case)
file_path = Path.join(["lib", web_snake_case, "controllers", "page_html", "home.html.heex"])
home_link = home_link(assigns)
injected_string = "#{@flash_string}\n#{home_link}"

modified_file_contents =
file_path
|> File.read!()
|> String.replace(@flash_string, injected_string)

File.write!(file_path, modified_file_contents)
end

defp home_link(assigns) do
web_pascal_case = Keyword.fetch!(assigns, :web_pascal_case)

"""
<#{inspect(web_pascal_case)}.NavigationComponents.navbar current_user={@current_user} />
"""
end
end
52 changes: 52 additions & 0 deletions lib/wac_gen/components.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
defmodule Wac.Gen.Components do
@moduledoc false

@template_path "../../templates/components"

@template_files %{
navigation_components: "navigation_components.ex",
navbar: "navigation/navbar.html.heex",
nav_link: "navigation/nav_link.html.heex"
}

@templates Map.keys(@template_files)

def copy_templates(assigns) do
for template <- @templates, do: copy_template(template, assigns)

update_flash_component(assigns)
end

def copy_template(template, assigns) when template in @templates and is_list(assigns) do
web_snake_case = Keyword.fetch!(assigns, :web_snake_case)
file_name = @template_files[template]
template_dir = Path.expand(@template_path, __DIR__)
source = Path.join([template_dir, file_name])
target = Path.join(["lib", web_snake_case, "components", file_name])

if String.contains?(file_name, ".heex") do
Mix.Generator.copy_file(source, target)
else
Mix.Generator.copy_template(source, target, assigns)
Code.format_file!(target)
end
end

def update_flash_component(assigns) do
# This could use `Sourceror.Zipper`, but the AST for the component
# and its `~H` would still require string replacement.
# If the core components are revised or redesigned, this would fail silently.

web_snake_case = Keyword.fetch!(assigns, :web_snake_case)
search_string = "fixed top-2 right-2 w-80"
replacement_string = "fixed top-14 right-2 w-80"
file_path = Path.join(["lib", web_snake_case, "components", "core_components.ex"])

updated_content =
file_path
|> File.read!()
|> String.replace(search_string, replacement_string)

File.write!(file_path, updated_content)
end
end
Loading