-
Notifications
You must be signed in to change notification settings - Fork 4
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
Client-side validation of personal numbers #10
Comments
Here is some code for validating and calculating the checksum of Swedish personal numbers using Luhns algorithm. There are 2 versions available, one recursive and one based on Enum. defmodule LuhnChecker do
def valid?(number) when is_integer(number) and number >= 0 do
digits = Enum.reverse(Integer.digits(number))
rem(sum_digits(digits, 1), 10) === 0
end
def checksum(number) when is_integer(number) and number >= 0 do
calculate_checksum(Integer.digits(number))
end
# alternate solution to calculating the checksum
defp checksum1(digits) do
sum =
digits
|> Enum.zip(Stream.cycle([2, 1]))
|> Enum.flat_map(fn {digit, factor} -> (digit * factor) |> Integer.digits() end)
|> Enum.sum()
ceil(sum / 10) * 10 - sum
end
defp calculate_checksum(digits) do
sum = sum_digits(digits, 2)
ceil(sum / 10) * 10 - sum
end
defp sum_digits([], _), do: 0
defp sum_digits([digit | digits], factor) do
sum_digits(
digits,
next_factor(factor)
) + checksum_digit(digit * factor)
end
defp checksum_digit(digit) when digit <= 9, do: digit
defp checksum_digit(digit), do: digit - 9
defp next_factor(1), do: 2
defp next_factor(2), do: 1
end |
First of all, thank you, @kwando, for taking the time. If you would like to take a crack at the rest of this issue, you're of course more than welcome to do so. |
There is actually already a HEX package for validating strings of numbers based on Luhn's algorithm: https://hex.pm/packages/luhn. I threw together the following example that validates:
def check_personal_number(personal_number)
when is_binary(personal_number) do
with true <- String.length(personal_number) == 12,
true <- check_personal_number_century(personal_number),
true <- personal_number |> String.slice(2, 10) |> Luhn.valid?() do
{:ok, personal_number}
else
false -> {:error, "Invalid personal number: #{personal_number}"}
end
end
def check_personal_number(nil) do
{:ok, nil}
end
defp check_personal_number_century("19" <> _), do: true
defp check_personal_number_century("20" <> _), do: true
defp check_personal_number_century(_), do: false Then of course, one could validate that the month and day are reasonable numbers. My mind goes to NimbleParsec and this Elixir Forum topic. Now, needless to say, there is the Gregorian calendar to observe in this regard. But, I guess that a rudimentary check is better than nothing to begin with? For testing there's the Swedish Tax Authority's published personal numbers reserved for testing. I guess one would want tests that cover the cases. With the current ones all Luhn validations will be performed on mere zeros. |
First stab. No previous experience in writing parsers so take it for what it is. defmodule MyParser do
import NimbleParsec
century =
choice([string("19"), string("20")])
year =
integer(2)
month =
~w(01 02 03 04 05 06 07 08 09 10 11 12)
|> Enum.map(&string/1)
|> choice()
day =
(~w(01 02 03 04 05 06 07 08 09) ++ Enum.map(10..31, &to_string/1))
|> Enum.map(&string/1)
|> choice()
defparsec :personal_number, century |> concat(year) |> concat(month) |> concat(day)
end |
Add personal number validation to the
new
methods inExBankID.Auth.Payload
andExBankID.Sign.Payload
.The text was updated successfully, but these errors were encountered: