Hi again! I decided to tackle this tonight.
I am not sure I understand correctly. What exactly is user.__metadata__.token
?
For now I’m just trying this from a test:
email = "test@test.com"
password = "supersecret"
payload = %{
"user" => %{
"email" => email,
"password" => password,
"password_confirmation" => password
}
}
conn = post(conn, ~p"/auth/user/password/register", payload)
data = json_response(conn, 200))
But I do not understand why the token is present in the user in the metadata. I created a MyApp.Accounts.Token
entity like the tutorial said, but now I understand that those are for server-side-only metadata about the tokens. Do they target that same token I found in my user struct metadata?
It seems so.
So now I would like the user to sign-up and then be able to sign-in from different machines and revoke their login from a specific machine.
In the AuthController I did this in the success
callback:
Token
|> Ash.Changeset.for_create(:store_token,
token: user.__metadata__.token,
purpose: "hello world"
)
|> Accounts.create!()
|> dbg()
I seems that the purpose is not relevant for Ash to work, though required. So I can add the machine based on the user agent or a custom header or field. And I can now write a basic test controller like this 
def check(conn, params) do
resp_payload =
case conn.assigns do
%{current_user: user} -> %{logged_in: true, as: user.email}
_ -> %{logged_in: false}
end
conn
|> put_status(200)
|> json(resp_payload)
end
And run my full test to see if it worked:
test "a user can authenticate via API using email and password", %{conn: base_conn} do
# Create a user from API
email = "test@test.com"
password = "supersecret"
payload = %{
"user" => %{
"email" => email,
"password" => password,
"password_confirmation" => password
}
}
conn = post(base_conn, ~p"/auth/user/password/register", payload)
_data_signup = json_response(conn, 200)
# Now sign in
conn = post(base_conn, ~p"/auth/user/password/sign_in", payload)
data_signin = json_response(conn, 200)
# Try the API without the token
conn = get(base_conn, ~p"/api/check")
data_checkwo = json_response(conn, 200)
assert false == data_checkwo["logged_in"]
# Try with
conn =
base_conn
|> Plug.Conn.put_req_header(
"authorization",
"Bearer #{Map.fetch!(data_signin, "bearer_token")}"
)
|> get(~p"/api/check")
data_checkwith = json_response(conn, 200)
assert true == data_checkwith["logged_in"]
assert email == data_checkwith["as"]
end
Now I realize that there is not really a question in my message
I’ll try to create another resource and have readable by the owner only.
I added this in my router:
import AshAuthentication.Plug.Helpers
And the new plug here:
pipeline :api do
plug :accepts, ["json"]
# Ash
plug :load_from_bearer
plug :set_actor, :user
end
But in the controller Ash.get_actor
is always nil
. I have seen that the plug only sets it in conn.private.ash.actor
. So I guess I should write a custom plug to forbid some api routes to be accessed without authentication. Is that how you would do it? I do not need any public data on those routes.
Or would you rather just always set an actor on the action, even if it is nil, and let the policies reject the calls?
Thank you for reading, sorry for that unstructured post
it took me a couple hours to write because I implemented the code along.