Skip to content

Commit

Permalink
Merge pull request #585 from amousset/arch_23243/hash_api_tokens_plugin
Browse files Browse the repository at this point in the history
Fixes #23243: Hash API tokens - plugin
  • Loading branch information
amousset committed Aug 16, 2023
2 parents 2ad8957 + ebeaea1 commit 7aaaee6
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 30 deletions.
67 changes: 47 additions & 20 deletions api-authorizations/src/main/elm/sources/UserApiToken.elm
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type alias Model =
{ contextPath : String
, token : Maybe Token
, error : Maybe (Html Msg)
, isNewToken : Bool
}


Expand Down Expand Up @@ -95,7 +96,7 @@ init : { contextPath : String } -> ( Model, Cmd Msg )
init flags =
let
initModel =
Model flags.contextPath Nothing Nothing
Model flags.contextPath Nothing Nothing False
in
( initModel
, getUserToken initModel
Expand Down Expand Up @@ -173,7 +174,7 @@ update msg model =
updateFromApiResult res model

CreateUserToken res ->
updateFromApiResult res model
updateFromApiResult res { model | isNewToken = True}

DeleteUserToken res ->
updateFromDelete res model
Expand Down Expand Up @@ -244,8 +245,8 @@ deleteUserToken model =
-- view when the token exists


tokenPresent : Token -> List (Html Msg)
tokenPresent token =
tokenPresent : Token -> Bool -> List (Html Msg)
tokenPresent token isNewToken =
let
( statusIcon, statusTxt, statusClass ) =
case token.enabled of
Expand All @@ -254,36 +255,62 @@ tokenPresent token =

False ->
( "fa-ban", "disabled", "text-info" )
hasClearTextToken = not (String.isEmpty token.token)
in
[ li []
[ if hasClearTextToken then
li []
[ a [ class "no-click" ]
[ span [ class "fa fa-key" ] []
, text "Your personal token: "
, div [ class "help-block" ] [ b [] [ text token.token ] ]
]
]
else
li []
[ a [ class "no-click" ]
[ span [ class "fa fa-key" ] []
, text "You have a personal token."
]
]
, if isNewToken then
li []
[ a [ class "no-click" ]
[ span [ class "fa fa-exclamation-triangle" ] []
, text "Copy it now as it will not be re-displayed"
]
]
else
if hasClearTextToken then
li []
[ a [ class "no-click" ]
[ span [ class "fa fa-key" ] []
, text "Your personal token: "
, div [ class "help-block" ] [ b [] [ text token.token ] ]
]
[ span [ class "fa fa-exclamation-triangle" ] []
, text "Deprecated token format, please re-create"
]
]
else
text ""
, li []
[ a [ class "no-click" ]
[ span [ class "fa fa-calendar" ] []
, text "Generated on "
, b [] [ text token.generationDate ]
]
[ span [ class "fa fa-calendar" ] []
, text "Generated on "
, b [] [ text token.generationDate ]
]
]
, li []
[ a [ class "no-click" ]
[ span [ class ("fa " ++ statusIcon) ] []
, text "Status: "
, b [ class ("text-capitalize " ++ statusClass) ] [ text statusTxt ]
]
[ span [ class ("fa " ++ statusIcon) ] []
, text "Status: "
, b [ class ("text-capitalize " ++ statusClass) ] [ text statusTxt ]
]
]
, li [ class "footer" ] [ a [ class "deleteToken", onClick DeleteButton ] [ text "Delete API Token" ] ]
, li [ class "footer" ] [ a [ class "deleteToken", onClick DeleteButton ] [ text "Delete API token" ] ]
]


tokenAbsent : Model -> List (Html Msg)
tokenAbsent model =
[ li [] [ a [ class "no-click no-token" ] [ text "You don't have an API token yet." ] ]
, li [ class "footer" ] [ a [ class "createToken", onClick CreateButton ] [ text "Create an API Token" ] ]
, li [ class "footer" ] [ a [ class "createToken", onClick CreateButton ] [ text "Create an API token" ] ]
]


Expand All @@ -299,7 +326,7 @@ view model =
]
++ (case model.token of
Just token ->
tokenPresent token
tokenPresent token model.isNewToken

Nothing ->
tokenAbsent model
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,14 @@ class UserApi(
val restExtractor = api.restExtractor
def process0(version: ApiVersion, path: ApiPath, req: Req, params: DefaultParams, authzToken: AuthzToken): LiftResponse = {
readApi.getById(ApiAccountId(authzToken.actor.name)).toBox match {
case Full(Some(token)) =>
val accounts: JValue = ("accounts" -> JArray(List(token.toJson)))
case Full(Some(account)) =>
val filtered = account.copy(token = if (account.token.isHashed) {
// Don't send hashes
ApiToken("")
} else {
account.token
})
val accounts: JValue = ("accounts" -> JArray(List(filtered.toJson)))
RestUtils.toJsonResponse(None, accounts)(schema.name, true)

case Full(None) =>
Expand All @@ -109,20 +115,31 @@ class UserApi(
val restExtractor = api.restExtractor
def process0(version: ApiVersion, path: ApiPath, req: Req, params: DefaultParams, authzToken: AuthzToken): LiftResponse = {
val now = DateTime.now
val secret = ApiToken.generate_secret(tokenGenerator)
val hash = ApiToken.hash(secret)
val account = ApiAccount(
ApiAccountId(authzToken.actor.name),
ApiAccountKind.User,
ApiAccountName(authzToken.actor.name),
ApiToken(tokenGenerator.newToken(32)),
ApiToken(hash),
s"API token for user '${authzToken.actor.name}'",
true,
isEnabled = true,
now,
now
)

writeApi.save(account, ModificationId(uuidGen.newUuid), authzToken.actor).toBox match {
case Full(token) =>
val accounts: JValue = ("accounts" -> JArray(List(token.toJson)))
case Full(account) =>
val accounts: JValue = ("accounts" -> JArray(
List(
account
.copy(
// Send clear text secret
token = ApiToken(secret)
)
.toJson
)
))
RestUtils.toJsonResponse(None, accounts)(schema.name, true)

case eb: EmptyBox =>
Expand All @@ -137,8 +154,8 @@ class UserApi(
val restExtractor = api.restExtractor
def process0(version: ApiVersion, path: ApiPath, req: Req, params: DefaultParams, authzToken: AuthzToken): LiftResponse = {
writeApi.delete(ApiAccountId(authzToken.actor.name), ModificationId(uuidGen.newUuid), authzToken.actor).toBox match {
case Full(token) =>
val accounts: JValue = ("accounts" -> ("id" -> token.value))
case Full(account) =>
val accounts: JValue = ("accounts" -> ("id" -> account.value))
RestUtils.toJsonResponse(None, accounts)(schema.name, true)

case eb: EmptyBox =>
Expand All @@ -153,8 +170,14 @@ class UserApi(
val restExtractor = api.restExtractor
def process0(version: ApiVersion, path: ApiPath, req: Req, params: DefaultParams, authzToken: AuthzToken): LiftResponse = {
readApi.getById(ApiAccountId(authzToken.actor.name)).toBox match {
case Full(Some(token)) =>
val accounts: JValue = ("accounts" -> JArray(List(token.toJson)))
case Full(Some(account)) =>
val filtered = account.copy(token = if (account.token.isHashed) {
// Don't send hashes
ApiToken("")
} else {
account.token
})
val accounts: JValue = ("accounts" -> JArray(List(filtered.toJson)))
RestUtils.toJsonResponse(None, accounts)(schema.name, true)

case Full(None) =>
Expand Down

0 comments on commit 7aaaee6

Please sign in to comment.