Added basic auth system, profiles, and start of models and data.

This commit is contained in:
Andrew Lalis 2024-07-31 13:20:17 -04:00
parent a24ab226c2
commit e198f45b92
21 changed files with 1349 additions and 42 deletions

View File

@ -14,3 +14,5 @@ finnow-api-test-*
*.o *.o
*.obj *.obj
*.lst *.lst
users/

View File

@ -0,0 +1,442 @@
Entity,Currency,AlphabeticCode,NumericCode,MinorUnit,WithdrawalDate
AFGHANISTAN,Afghani,AFN,971,2,
ÅLAND ISLANDS,Euro,EUR,978,2,
ALBANIA,Lek,ALL,008,2,
ALGERIA,Algerian Dinar,DZD,012,2,
AMERICAN SAMOA,US Dollar,USD,840,2,
ANDORRA,Euro,EUR,978,2,
ANGOLA,Kwanza,AOA,973,2,
ANGUILLA,East Caribbean Dollar,XCD,951,2,
ANTARCTICA,No universal currency,,,,
ANTIGUA AND BARBUDA,East Caribbean Dollar,XCD,951,2,
ARGENTINA,Argentine Peso,ARS,032,2,
ARMENIA,Armenian Dram,AMD,051,2,
ARUBA,Aruban Florin,AWG,533,2,
AUSTRALIA,Australian Dollar,AUD,036,2,
AUSTRIA,Euro,EUR,978,2,
AZERBAIJAN,Azerbaijan Manat,AZN,944,2,
BAHAMAS (THE),Bahamian Dollar,BSD,044,2,
BAHRAIN,Bahraini Dinar,BHD,048,3,
BANGLADESH,Taka,BDT,050,2,
BARBADOS,Barbados Dollar,BBD,052,2,
BELARUS,Belarusian Ruble,BYN,933,2,
BELGIUM,Euro,EUR,978,2,
BELIZE,Belize Dollar,BZD,084,2,
BENIN,CFA Franc BCEAO,XOF,952,0,
BERMUDA,Bermudian Dollar,BMD,060,2,
BHUTAN,Indian Rupee,INR,356,2,
BHUTAN,Ngultrum,BTN,064,2,
BOLIVIA (PLURINATIONAL STATE OF),Boliviano,BOB,068,2,
BOLIVIA (PLURINATIONAL STATE OF),Mvdol,BOV,984,2,
"BONAIRE, SINT EUSTATIUS AND SABA",US Dollar,USD,840,2,
BOSNIA AND HERZEGOVINA,Convertible Mark,BAM,977,2,
BOTSWANA,Pula,BWP,072,2,
BOUVET ISLAND,Norwegian Krone,NOK,578,2,
BRAZIL,Brazilian Real,BRL,986,2,
BRITISH INDIAN OCEAN TERRITORY (THE),US Dollar,USD,840,2,
BRUNEI DARUSSALAM,Brunei Dollar,BND,096,2,
BULGARIA,Bulgarian Lev,BGN,975,2,
BURKINA FASO,CFA Franc BCEAO,XOF,952,0,
BURUNDI,Burundi Franc,BIF,108,0,
CABO VERDE,Cabo Verde Escudo,CVE,132,2,
CAMBODIA,Riel,KHR,116,2,
CAMEROON,CFA Franc BEAC,XAF,950,0,
CANADA,Canadian Dollar,CAD,124,2,
CAYMAN ISLANDS (THE),Cayman Islands Dollar,KYD,136,2,
CENTRAL AFRICAN REPUBLIC (THE),CFA Franc BEAC,XAF,950,0,
CHAD,CFA Franc BEAC,XAF,950,0,
CHILE,Chilean Peso,CLP,152,0,
CHILE,Unidad de Fomento,CLF,990,4,
CHINA,Yuan Renminbi,CNY,156,2,
CHRISTMAS ISLAND,Australian Dollar,AUD,036,2,
COCOS (KEELING) ISLANDS (THE),Australian Dollar,AUD,036,2,
COLOMBIA,Colombian Peso,COP,170,2,
COLOMBIA,Unidad de Valor Real,COU,970,2,
COMOROS (THE),Comorian Franc ,KMF,174,0,
CONGO (THE DEMOCRATIC REPUBLIC OF THE),Congolese Franc,CDF,976,2,
CONGO (THE),CFA Franc BEAC,XAF,950,0,
COOK ISLANDS (THE),New Zealand Dollar,NZD,554,2,
COSTA RICA,Costa Rican Colon,CRC,188,2,
CÔTE D'IVOIRE,CFA Franc BCEAO,XOF,952,0,
CROATIA,Kuna,HRK,191,2,
CUBA,Cuban Peso,CUP,192,2,
CUBA,Peso Convertible,CUC,931,2,
CURAÇAO,Netherlands Antillean Guilder,ANG,532,2,
CYPRUS,Euro,EUR,978,2,
CZECHIA,Czech Koruna,CZK,203,2,
DENMARK,Danish Krone,DKK,208,2,
DJIBOUTI,Djibouti Franc,DJF,262,0,
DOMINICA,East Caribbean Dollar,XCD,951,2,
DOMINICAN REPUBLIC (THE),Dominican Peso,DOP,214,2,
ECUADOR,US Dollar,USD,840,2,
EGYPT,Egyptian Pound,EGP,818,2,
EL SALVADOR,El Salvador Colon,SVC,222,2,
EL SALVADOR,US Dollar,USD,840,2,
EQUATORIAL GUINEA,CFA Franc BEAC,XAF,950,0,
ERITREA,Nakfa,ERN,232,2,
ESTONIA,Euro,EUR,978,2,
ESWATINI,Lilangeni,SZL,748,2,
ETHIOPIA,Ethiopian Birr,ETB,230,2,
EUROPEAN UNION,Euro,EUR,978,2,
"FALKLAND ISLANDS (THE) [MALVINAS]",Falkland Islands Pound,FKP,238,2,
FAROE ISLANDS (THE),Danish Krone,DKK,208,2,
FIJI,Fiji Dollar,FJD,242,2,
FINLAND,Euro,EUR,978,2,
FRANCE,Euro,EUR,978,2,
FRENCH GUIANA,Euro,EUR,978,2,
FRENCH POLYNESIA,CFP Franc,XPF,953,0,
FRENCH SOUTHERN TERRITORIES (THE),Euro,EUR,978,2,
GABON,CFA Franc BEAC,XAF,950,0,
GAMBIA (THE),Dalasi,GMD,270,2,
GEORGIA,Lari,GEL,981,2,
GERMANY,Euro,EUR,978,2,
GHANA,Ghana Cedi,GHS,936,2,
GIBRALTAR,Gibraltar Pound,GIP,292,2,
GREECE,Euro,EUR,978,2,
GREENLAND,Danish Krone,DKK,208,2,
GRENADA,East Caribbean Dollar,XCD,951,2,
GUADELOUPE,Euro,EUR,978,2,
GUAM,US Dollar,USD,840,2,
GUATEMALA,Quetzal,GTQ,320,2,
GUERNSEY,Pound Sterling,GBP,826,2,
GUINEA,Guinean Franc,GNF,324,0,
GUINEA-BISSAU,CFA Franc BCEAO,XOF,952,0,
GUYANA,Guyana Dollar,GYD,328,2,
HAITI,Gourde,HTG,332,2,
HAITI,US Dollar,USD,840,2,
HEARD ISLAND AND McDONALD ISLANDS,Australian Dollar,AUD,036,2,
HOLY SEE (THE),Euro,EUR,978,2,
HONDURAS,Lempira,HNL,340,2,
HONG KONG,Hong Kong Dollar,HKD,344,2,
HUNGARY,Forint,HUF,348,2,
ICELAND,Iceland Krona,ISK,352,0,
INDIA,Indian Rupee,INR,356,2,
INDONESIA,Rupiah,IDR,360,2,
INTERNATIONAL MONETARY FUND (IMF),SDR (Special Drawing Right),XDR,960,-,
IRAN (ISLAMIC REPUBLIC OF),Iranian Rial,IRR,364,2,
IRAQ,Iraqi Dinar,IQD,368,3,
IRELAND,Euro,EUR,978,2,
ISLE OF MAN,Pound Sterling,GBP,826,2,
ISRAEL,New Israeli Sheqel,ILS,376,2,
ITALY,Euro,EUR,978,2,
JAMAICA,Jamaican Dollar,JMD,388,2,
JAPAN,Yen,JPY,392,0,
JERSEY,Pound Sterling,GBP,826,2,
JORDAN,Jordanian Dinar,JOD,400,3,
KAZAKHSTAN,Tenge,KZT,398,2,
KENYA,Kenyan Shilling,KES,404,2,
KIRIBATI,Australian Dollar,AUD,036,2,
KOREA (THE DEMOCRATIC PEOPLE'S REPUBLIC OF),North Korean Won,KPW,408,2,
KOREA (THE REPUBLIC OF),Won,KRW,410,0,
KUWAIT,Kuwaiti Dinar,KWD,414,3,
KYRGYZSTAN,Som,KGS,417,2,
LAO PEOPLE'S DEMOCRATIC REPUBLIC (THE),Lao Kip,LAK,418,2,
LATVIA,Euro,EUR,978,2,
LEBANON,Lebanese Pound,LBP,422,2,
LESOTHO,Loti,LSL,426,2,
LESOTHO,Rand,ZAR,710,2,
LIBERIA,Liberian Dollar,LRD,430,2,
LIBYA,Libyan Dinar,LYD,434,3,
LIECHTENSTEIN,Swiss Franc,CHF,756,2,
LITHUANIA,Euro,EUR,978,2,
LUXEMBOURG,Euro,EUR,978,2,
MACAO,Pataca,MOP,446,2,
NORTH MACEDONIA,Denar,MKD,807,2,
MADAGASCAR,Malagasy Ariary,MGA,969,2,
MALAWI,Malawi Kwacha,MWK,454,2,
MALAYSIA,Malaysian Ringgit,MYR,458,2,
MALDIVES,Rufiyaa,MVR,462,2,
MALI,CFA Franc BCEAO,XOF,952,0,
MALTA,Euro,EUR,978,2,
MARSHALL ISLANDS (THE),US Dollar,USD,840,2,
MARTINIQUE,Euro,EUR,978,2,
MAURITANIA,Ouguiya,MRU,929,2,
MAURITIUS,Mauritius Rupee,MUR,480,2,
MAYOTTE,Euro,EUR,978,2,
MEMBER COUNTRIES OF THE AFRICAN DEVELOPMENT BANK GROUP,ADB Unit of Account,XUA,965,-,
MEXICO,Mexican Peso,MXN,484,2,
MEXICO,Mexican Unidad de Inversion (UDI),MXV,979,2,
MICRONESIA (FEDERATED STATES OF),US Dollar,USD,840,2,
MOLDOVA (THE REPUBLIC OF),Moldovan Leu,MDL,498,2,
MONACO,Euro,EUR,978,2,
MONGOLIA,Tugrik,MNT,496,2,
MONTENEGRO,Euro,EUR,978,2,
MONTSERRAT,East Caribbean Dollar,XCD,951,2,
MOROCCO,Moroccan Dirham,MAD,504,2,
MOZAMBIQUE,Mozambique Metical,MZN,943,2,
MYANMAR,Kyat,MMK,104,2,
NAMIBIA,Namibia Dollar,NAD,516,2,
NAMIBIA,Rand,ZAR,710,2,
NAURU,Australian Dollar,AUD,036,2,
NEPAL,Nepalese Rupee,NPR,524,2,
NETHERLANDS (THE),Euro,EUR,978,2,
NEW CALEDONIA,CFP Franc,XPF,953,0,
NEW ZEALAND,New Zealand Dollar,NZD,554,2,
NICARAGUA,Cordoba Oro,NIO,558,2,
NIGER (THE),CFA Franc BCEAO,XOF,952,0,
NIGERIA,Naira,NGN,566,2,
NIUE,New Zealand Dollar,NZD,554,2,
NORFOLK ISLAND,Australian Dollar,AUD,036,2,
NORTHERN MARIANA ISLANDS (THE),US Dollar,USD,840,2,
NORWAY,Norwegian Krone,NOK,578,2,
OMAN,Rial Omani,OMR,512,3,
PAKISTAN,Pakistan Rupee,PKR,586,2,
PALAU,US Dollar,USD,840,2,
"PALESTINE, STATE OF",No universal currency,,,,
PANAMA,Balboa,PAB,590,2,
PANAMA,US Dollar,USD,840,2,
PAPUA NEW GUINEA,Kina,PGK,598,2,
PARAGUAY,Guarani,PYG,600,0,
PERU,Sol,PEN,604,2,
PHILIPPINES (THE),Philippine Peso,PHP,608,2,
PITCAIRN,New Zealand Dollar,NZD,554,2,
POLAND,Zloty,PLN,985,2,
PORTUGAL,Euro,EUR,978,2,
PUERTO RICO,US Dollar,USD,840,2,
QATAR,Qatari Rial,QAR,634,2,
RÉUNION,Euro,EUR,978,2,
ROMANIA,Romanian Leu,RON,946,2,
RUSSIAN FEDERATION (THE),Russian Ruble,RUB,643,2,
RWANDA,Rwanda Franc,RWF,646,0,
SAINT BARTHÉLEMY,Euro,EUR,978,2,
"SAINT HELENA, ASCENSION AND TRISTAN DA CUNHA",Saint Helena Pound,SHP,654,2,
SAINT KITTS AND NEVIS,East Caribbean Dollar,XCD,951,2,
SAINT LUCIA,East Caribbean Dollar,XCD,951,2,
SAINT MARTIN (FRENCH PART),Euro,EUR,978,2,
SAINT PIERRE AND MIQUELON,Euro,EUR,978,2,
SAINT VINCENT AND THE GRENADINES,East Caribbean Dollar,XCD,951,2,
SAMOA,Tala,WST,882,2,
SAN MARINO,Euro,EUR,978,2,
SAO TOME AND PRINCIPE,Dobra,STN,930,2,
SAUDI ARABIA,Saudi Riyal,SAR,682,2,
SENEGAL,CFA Franc BCEAO,XOF,952,0,
SERBIA,Serbian Dinar,RSD,941,2,
SEYCHELLES,Seychelles Rupee,SCR,690,2,
SIERRA LEONE,Leone,SLL,694,2,
SINGAPORE,Singapore Dollar,SGD,702,2,
SINT MAARTEN (DUTCH PART),Netherlands Antillean Guilder,ANG,532,2,
"SISTEMA UNITARIO DE COMPENSACION REGIONAL DE PAGOS ""SUCRE""",Sucre,XSU,994,-,
SLOVAKIA,Euro,EUR,978,2,
SLOVENIA,Euro,EUR,978,2,
SOLOMON ISLANDS,Solomon Islands Dollar,SBD,090,2,
SOMALIA,Somali Shilling,SOS,706,2,
SOUTH AFRICA,Rand,ZAR,710,2,
SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS,No universal currency,,,,
SOUTH SUDAN,South Sudanese Pound,SSP,728,2,
SPAIN,Euro,EUR,978,2,
SRI LANKA,Sri Lanka Rupee,LKR,144,2,
SUDAN (THE),Sudanese Pound,SDG,938,2,
SURINAME,Surinam Dollar,SRD,968,2,
SVALBARD AND JAN MAYEN,Norwegian Krone,NOK,578,2,
SWEDEN,Swedish Krona,SEK,752,2,
SWITZERLAND,Swiss Franc,CHF,756,2,
SWITZERLAND,WIR Euro,CHE,947,2,
SWITZERLAND,WIR Franc,CHW,948,2,
SYRIAN ARAB REPUBLIC,Syrian Pound,SYP,760,2,
TAIWAN (PROVINCE OF CHINA),New Taiwan Dollar,TWD,901,2,
TAJIKISTAN,Somoni,TJS,972,2,
"TANZANIA, UNITED REPUBLIC OF",Tanzanian Shilling,TZS,834,2,
THAILAND,Baht,THB,764,2,
TIMOR-LESTE,US Dollar,USD,840,2,
TOGO,CFA Franc BCEAO,XOF,952,0,
TOKELAU,New Zealand Dollar,NZD,554,2,
TONGA,Pa'anga,TOP,776,2,
TRINIDAD AND TOBAGO,Trinidad and Tobago Dollar,TTD,780,2,
TUNISIA,Tunisian Dinar,TND,788,3,
TURKEY,Turkish Lira,TRY,949,2,
TURKMENISTAN,Turkmenistan New Manat,TMT,934,2,
TURKS AND CAICOS ISLANDS (THE),US Dollar,USD,840,2,
TUVALU,Australian Dollar,AUD,036,2,
UGANDA,Uganda Shilling,UGX,800,0,
UKRAINE,Hryvnia,UAH,980,2,
UNITED ARAB EMIRATES (THE),UAE Dirham,AED,784,2,
UNITED KINGDOM OF GREAT BRITAIN AND NORTHERN IRELAND (THE),Pound Sterling,GBP,826,2,
UNITED STATES MINOR OUTLYING ISLANDS (THE),US Dollar,USD,840,2,
UNITED STATES OF AMERICA (THE),US Dollar,USD,840,2,
UNITED STATES OF AMERICA (THE),US Dollar (Next day),USN,997,2,
URUGUAY,Peso Uruguayo,UYU,858,2,
URUGUAY,Uruguay Peso en Unidades Indexadas (UI),UYI,940,0,
URUGUAY,Unidad Previsional,UYW,927,4,
UZBEKISTAN,Uzbekistan Sum,UZS,860,2,
VANUATU,Vatu,VUV,548,0,
VENEZUELA (BOLIVARIAN REPUBLIC OF),Bolívar Soberano,VES,928,2,
VIET NAM,Dong,VND,704,0,
VIRGIN ISLANDS (BRITISH),US Dollar,USD,840,2,
VIRGIN ISLANDS (U.S.),US Dollar,USD,840,2,
WALLIS AND FUTUNA,CFP Franc,XPF,953,0,
WESTERN SAHARA,Moroccan Dirham,MAD,504,2,
YEMEN,Yemeni Rial,YER,886,2,
ZAMBIA,Zambian Kwacha,ZMW,967,2,
ZIMBABWE,Zimbabwe Dollar,ZWL,932,2,
ZZ01_Bond Markets Unit European_EURCO,Bond Markets Unit European Composite Unit (EURCO),XBA,955,-,
ZZ02_Bond Markets Unit European_EMU-6,Bond Markets Unit European Monetary Unit (E.M.U.-6),XBB,956,-,
ZZ03_Bond Markets Unit European_EUA-9,Bond Markets Unit European Unit of Account 9 (E.U.A.-9),XBC,957,-,
ZZ04_Bond Markets Unit European_EUA-17,Bond Markets Unit European Unit of Account 17 (E.U.A.-17),XBD,958,-,
ZZ06_Testing_Code,Codes specifically reserved for testing purposes,XTS,963,-,
ZZ07_No_Currency,The codes assigned for transactions where no currency is involved,XXX,999,-,
ZZ08_Gold,Gold,XAU,959,-,
ZZ09_Palladium,Palladium,XPD,964,-,
ZZ10_Platinum,Platinum,XPT,962,-,
ZZ11_Silver,Silver,XAG,961,-,
AFGHANISTAN,Afghani,AFA,004,,2003-01
ÅLAND ISLANDS,Markka,FIM,246,,2002-03
ALBANIA,Old Lek,ALK,008,,1989-12
ANDORRA,Andorran Peseta,ADP,020,,2003-07
ANDORRA,Spanish Peseta,ESP,724,,2002-03
ANDORRA,French Franc,FRF,250,,2002-03
ANGOLA,Kwanza,AOK,024,,1991-03
ANGOLA,New Kwanza,AON,024,,2000-02
ANGOLA,Kwanza Reajustado,AOR,982,,2000-02
ARGENTINA,Austral,ARA,032,,1992-01
ARGENTINA,Peso Argentino,ARP,032,,1985-07
ARGENTINA,Peso,ARY,032,,1989 to 1990
ARMENIA,Russian Ruble,RUR,810,,1994-08
AUSTRIA,Schilling,ATS,040,,2002-03
AZERBAIJAN,Azerbaijan Manat,AYM,945,,2005-10
AZERBAIJAN,Azerbaijanian Manat,AZM,031,,2005-12
AZERBAIJAN,Russian Ruble,RUR,810,,1994-08
BELARUS,Belarusian Ruble,BYB,112,,2001-01
BELARUS,Belarusian Ruble,BYR,974,,2017-01
BELARUS,Russian Ruble,RUR,810,,1994-06
BELGIUM,Convertible Franc,BEC,993,,1990-03
BELGIUM,Belgian Franc,BEF,056,,2002-03
BELGIUM,Financial Franc,BEL,992,,1990-03
BOLIVIA,Peso boliviano,BOP,068,,1987-02
BOSNIA AND HERZEGOVINA,Dinar,BAD,070,,1998-07
BRAZIL,Cruzeiro,BRB,076,,1986-03
BRAZIL,Cruzado,BRC,076,,1989-02
BRAZIL,Cruzeiro,BRE,076,,1993-03
BRAZIL,New Cruzado,BRN,076,,1990-03
BRAZIL,Cruzeiro Real,BRR,987,,1994-07
BULGARIA,Lev A/52,BGJ,100,,1989 to 1990
BULGARIA,Lev A/62,BGK,100,,1989 to 1990
BULGARIA,Lev,BGL,100,,2003-11
BURMA,Kyat,BUK,104,,1990-02
CROATIA,Croatian Dinar,HRD,191,,1995-01
CROATIA,Croatian Kuna,HRK,191,,2015-06
CYPRUS,Cyprus Pound,CYP,196,,2008-01
CZECHOSLOVAKIA,Krona A/53,CSJ,203,,1989 to 1990
CZECHOSLOVAKIA,Koruna,CSK,200,,1993-03
ECUADOR,Sucre,ECS,218,,2000-09
ECUADOR,Unidad de Valor Constante (UVC),ECV,983,,2000-09
EQUATORIAL GUINEA,Ekwele,GQE,226,,1986-06
ESTONIA,Kroon,EEK,233,,2011-01
EUROPEAN MONETARY CO-OPERATION FUND (EMCF),European Currency Unit (E.C.U),XEU,954,,1999-01
FINLAND,Markka,FIM,246,,2002-03
FRANCE,French Franc,FRF,250,,2002-03
FRENCH GUIANA,French Franc,FRF,250,,2002-03
FRENCH SOUTHERN TERRITORIES,French Franc,FRF,250,,2002-03
GEORGIA,Georgian Coupon,GEK,268,,1995-10
GEORGIA,Russian Ruble,RUR,810,,1994-04
GERMAN DEMOCRATIC REPUBLIC,Mark der DDR,DDM,278,,1990-07 to 1990-09
GERMANY,Deutsche Mark,DEM,276,,2002-03
GHANA,Cedi,GHC,288,,2008-01
GHANA,Ghana Cedi,GHP,939,,2007-06
GREECE,Drachma,GRD,300,,2002-03
GUADELOUPE,French Franc,FRF,250,,2002-03
GUINEA,Syli,GNE,324,,1989-12
GUINEA,Syli,GNS,324,,1986-02
GUINEA-BISSAU,Guinea Escudo,GWE,624,,1978 to 1981
GUINEA-BISSAU,Guinea-Bissau Peso,GWP,624,,1997-05
HOLY SEE (VATICAN CITY STATE),Italian Lira,ITL,380,,2002-03
ICELAND,Old Krona,ISJ,352,,1989 to 1990
IRELAND,Irish Pound,IEP,372,,2002-03
ISRAEL,Pound,ILP,376,,1978 to 1981
ISRAEL,Old Shekel,ILR,376,,1989 to 1990
ITALY,Italian Lira,ITL,380,,2002-03
KAZAKHSTAN,Russian Ruble,RUR,810,,1994-05
KYRGYZSTAN,Russian Ruble,RUR,810,,1993-01
LAO,Pathet Lao Kip,LAJ,418,,1979-12
LATVIA,Latvian Lats,LVL,428,,2014-01
LATVIA,Latvian Ruble,LVR,428,,1994-12
LESOTHO,Loti,LSM,426,,1985-05
LESOTHO,Financial Rand,ZAL,991,,1995-03
LITHUANIA,Lithuanian Litas,LTL,440,,2014-12
LITHUANIA,Talonas,LTT,440,,1993-07
LUXEMBOURG,Luxembourg Convertible Franc,LUC,989,,1990-03
LUXEMBOURG,Luxembourg Franc,LUF,442,,2002-03
LUXEMBOURG,Luxembourg Financial Franc,LUL,988,,1990-03
MADAGASCAR,Malagasy Franc,MGF,450,,2004-12
MALAWI,Kwacha,MWK,454,,2016-02
MALDIVES,Maldive Rupee,MVQ,462,,1989-12
MALI,Mali Franc,MLF,466,,1984-11
MALTA,Maltese Lira,MTL,470,,2008-01
MALTA,Maltese Pound,MTP,470,,1983-06
MARTINIQUE,French Franc,FRF,250,,2002-03
MAURITANIA,Ouguiya,MRO,478,,2017-12
MAYOTTE,French Franc,FRF,250,,2002-03
MEXICO,Mexican Peso,MXP,484,,1993-01
"MOLDOVA, REPUBLIC OF",Russian Ruble,RUR,810,,1993-12
MONACO,French Franc,FRF,250,,2002-03
MOZAMBIQUE,Mozambique Escudo,MZE,508,,1978 to 1981
MOZAMBIQUE,Mozambique Metical,MZM,508,,2006-06
NETHERLANDS,Netherlands Guilder,NLG,528,,2002-03
NETHERLANDS ANTILLES,Netherlands Antillean Guilder,ANG,532,,2010-10
NICARAGUA,Cordoba,NIC,558,,1990-10
PERU,Sol,PEH,604,,1989 to 1990
PERU,Inti,PEI,604,,1991-07
PERU,Nuevo Sol ,PEN,604,,2015-12
PERU,Sol,PES,604,,1986-02
POLAND,Zloty,PLZ,616,,1997-01
PORTUGAL,Portuguese Escudo,PTE,620,,2002-03
RÉUNION,French Franc,FRF,250,,2002-03
ROMANIA,Leu A/52,ROK,642,,1989 to 1990
ROMANIA,Old Leu,ROL,642,,2005-06
ROMANIA,New Romanian Leu ,RON,946,,2015-06
RUSSIAN FEDERATION,Russian Ruble,RUR,810,,2004-01
SAINT MARTIN,French Franc,FRF,250,,1999-01
SAINT PIERRE AND MIQUELON,French Franc,FRF,250,,2002-03
SAINT-BARTHÉLEMY,French Franc,FRF,250,,1999-01
SAN MARINO,Italian Lira,ITL,380,,2002-03
SAO TOME AND PRINCIPE,Dobra,STD,678,,2017-12
SERBIA AND MONTENEGRO,Serbian Dinar,CSD,891,,2006-10
SERBIA AND MONTENEGRO,Euro,EUR,978,,2006-10
SLOVAKIA,Slovak Koruna,SKK,703,,2009-01
SLOVENIA,Tolar,SIT,705,,2007-01
SOUTH AFRICA,Financial Rand,ZAL,991,,1995-03
SOUTH SUDAN,Sudanese Pound,SDG,938,,2012-09
SOUTHERN RHODESIA,Rhodesian Dollar,RHD,716,,1978 to 1981
SPAIN,Spanish Peseta,ESA,996,,1978 to 1981
SPAIN,"""A"" Account (convertible Peseta Account)",ESB,995,,1994-12
SPAIN,Spanish Peseta,ESP,724,,2002-03
SUDAN,Sudanese Dinar,SDD,736,,2007-07
SUDAN,Sudanese Pound,SDP,736,,1998-06
SURINAME,Surinam Guilder,SRG,740,,2003-12
SWAZILAND,Lilangeni,SZL,748,,2018-08
SWITZERLAND,WIR Franc (for electronic),CHC,948,,2004-11
TAJIKISTAN,Russian Ruble,RUR,810,,1995-05
TAJIKISTAN,Tajik Ruble,TJR,762,,2001-04
TIMOR-LESTE,Rupiah,IDR,360,,2002-07
TIMOR-LESTE,Timor Escudo,TPE,626,,2002-11
TURKEY,Old Turkish Lira,TRL,792,,2005-12
TURKEY,New Turkish Lira,TRY,949,,2009-01
TURKMENISTAN,Russian Ruble,RUR,810,,1993-10
TURKMENISTAN,Turkmenistan Manat,TMM,795,,2009-01
UGANDA,Uganda Shilling,UGS,800,,1987-05
UGANDA,Old Shilling,UGW,800,,1989 to 1990
UKRAINE,Karbovanet,UAK,804,,1996-09
UNION OF SOVIET SOCIALIST REPUBLICS,Rouble,SUR,810,,1990-12
UNITED STATES,US Dollar (Same day),USS,998,,2014-03
URUGUAY,Old Uruguay Peso,UYN,858,,1989-12
URUGUAY,Uruguayan Peso,UYP,858,,1993-03
UZBEKISTAN,Russian Ruble,RUR,810,,1994-07
VENEZUELA,Bolivar,VEB,862,,2008-01
VENEZUELA,Bolivar Fuerte,VEF,937,,2011-12
VENEZUELA (BOLIVARIAN REPUBLIC OF),Bolivar,VEF,937,,2016-02
VENEZUELA (BOLIVARIAN REPUBLIC OF),Bolívar,VEF,937,,2018-08
VIETNAM,Old Dong,VNC,704,,1989-1990
"YEMEN, DEMOCRATIC",Yemeni Dinar,YDD,720,,1991-09
YUGOSLAVIA,New Yugoslavian Dinar,YUD,890,,1990-01
YUGOSLAVIA,New Dinar,YUM,891,,2003-07
YUGOSLAVIA,Yugoslavian Dinar,YUN,890,,1995-11
ZAIRE,New Zaire,ZRN,180,,1999-06
ZAIRE,Zaire,ZRZ,180,,1994-02
ZAMBIA,Zambian Kwacha,ZMK,894,,2012-12
ZIMBABWE,Rhodesian Dollar,ZWC,716,,1989-12
ZIMBABWE,Zimbabwe Dollar (old),ZWD,716,,2006-08
ZIMBABWE,Zimbabwe Dollar,ZWD,716,,2008-08
ZIMBABWE,Zimbabwe Dollar (new),ZWN,942,,2006-09
ZIMBABWE,Zimbabwe Dollar,ZWR,935,,2009-06
ZZ01_Gold-Franc,Gold-Franc,XFO,,,2006-10
ZZ02_RINET Funds Code,RINET Funds Code,XRE,,,1999-11
ZZ05_UIC-Franc,UIC-Franc,XFU,,,2013-11
1 Entity Currency AlphabeticCode NumericCode MinorUnit WithdrawalDate
2 AFGHANISTAN Afghani AFN 971 2
3 ÅLAND ISLANDS Euro EUR 978 2
4 ALBANIA Lek ALL 008 2
5 ALGERIA Algerian Dinar DZD 012 2
6 AMERICAN SAMOA US Dollar USD 840 2
7 ANDORRA Euro EUR 978 2
8 ANGOLA Kwanza AOA 973 2
9 ANGUILLA East Caribbean Dollar XCD 951 2
10 ANTARCTICA No universal currency
11 ANTIGUA AND BARBUDA East Caribbean Dollar XCD 951 2
12 ARGENTINA Argentine Peso ARS 032 2
13 ARMENIA Armenian Dram AMD 051 2
14 ARUBA Aruban Florin AWG 533 2
15 AUSTRALIA Australian Dollar AUD 036 2
16 AUSTRIA Euro EUR 978 2
17 AZERBAIJAN Azerbaijan Manat AZN 944 2
18 BAHAMAS (THE) Bahamian Dollar BSD 044 2
19 BAHRAIN Bahraini Dinar BHD 048 3
20 BANGLADESH Taka BDT 050 2
21 BARBADOS Barbados Dollar BBD 052 2
22 BELARUS Belarusian Ruble BYN 933 2
23 BELGIUM Euro EUR 978 2
24 BELIZE Belize Dollar BZD 084 2
25 BENIN CFA Franc BCEAO XOF 952 0
26 BERMUDA Bermudian Dollar BMD 060 2
27 BHUTAN Indian Rupee INR 356 2
28 BHUTAN Ngultrum BTN 064 2
29 BOLIVIA (PLURINATIONAL STATE OF) Boliviano BOB 068 2
30 BOLIVIA (PLURINATIONAL STATE OF) Mvdol BOV 984 2
31 BONAIRE, SINT EUSTATIUS AND SABA US Dollar USD 840 2
32 BOSNIA AND HERZEGOVINA Convertible Mark BAM 977 2
33 BOTSWANA Pula BWP 072 2
34 BOUVET ISLAND Norwegian Krone NOK 578 2
35 BRAZIL Brazilian Real BRL 986 2
36 BRITISH INDIAN OCEAN TERRITORY (THE) US Dollar USD 840 2
37 BRUNEI DARUSSALAM Brunei Dollar BND 096 2
38 BULGARIA Bulgarian Lev BGN 975 2
39 BURKINA FASO CFA Franc BCEAO XOF 952 0
40 BURUNDI Burundi Franc BIF 108 0
41 CABO VERDE Cabo Verde Escudo CVE 132 2
42 CAMBODIA Riel KHR 116 2
43 CAMEROON CFA Franc BEAC XAF 950 0
44 CANADA Canadian Dollar CAD 124 2
45 CAYMAN ISLANDS (THE) Cayman Islands Dollar KYD 136 2
46 CENTRAL AFRICAN REPUBLIC (THE) CFA Franc BEAC XAF 950 0
47 CHAD CFA Franc BEAC XAF 950 0
48 CHILE Chilean Peso CLP 152 0
49 CHILE Unidad de Fomento CLF 990 4
50 CHINA Yuan Renminbi CNY 156 2
51 CHRISTMAS ISLAND Australian Dollar AUD 036 2
52 COCOS (KEELING) ISLANDS (THE) Australian Dollar AUD 036 2
53 COLOMBIA Colombian Peso COP 170 2
54 COLOMBIA Unidad de Valor Real COU 970 2
55 COMOROS (THE) Comorian Franc KMF 174 0
56 CONGO (THE DEMOCRATIC REPUBLIC OF THE) Congolese Franc CDF 976 2
57 CONGO (THE) CFA Franc BEAC XAF 950 0
58 COOK ISLANDS (THE) New Zealand Dollar NZD 554 2
59 COSTA RICA Costa Rican Colon CRC 188 2
60 CÔTE D'IVOIRE CFA Franc BCEAO XOF 952 0
61 CROATIA Kuna HRK 191 2
62 CUBA Cuban Peso CUP 192 2
63 CUBA Peso Convertible CUC 931 2
64 CURAÇAO Netherlands Antillean Guilder ANG 532 2
65 CYPRUS Euro EUR 978 2
66 CZECHIA Czech Koruna CZK 203 2
67 DENMARK Danish Krone DKK 208 2
68 DJIBOUTI Djibouti Franc DJF 262 0
69 DOMINICA East Caribbean Dollar XCD 951 2
70 DOMINICAN REPUBLIC (THE) Dominican Peso DOP 214 2
71 ECUADOR US Dollar USD 840 2
72 EGYPT Egyptian Pound EGP 818 2
73 EL SALVADOR El Salvador Colon SVC 222 2
74 EL SALVADOR US Dollar USD 840 2
75 EQUATORIAL GUINEA CFA Franc BEAC XAF 950 0
76 ERITREA Nakfa ERN 232 2
77 ESTONIA Euro EUR 978 2
78 ESWATINI Lilangeni SZL 748 2
79 ETHIOPIA Ethiopian Birr ETB 230 2
80 EUROPEAN UNION Euro EUR 978 2
81 FALKLAND ISLANDS (THE) [MALVINAS] Falkland Islands Pound FKP 238 2
82 FAROE ISLANDS (THE) Danish Krone DKK 208 2
83 FIJI Fiji Dollar FJD 242 2
84 FINLAND Euro EUR 978 2
85 FRANCE Euro EUR 978 2
86 FRENCH GUIANA Euro EUR 978 2
87 FRENCH POLYNESIA CFP Franc XPF 953 0
88 FRENCH SOUTHERN TERRITORIES (THE) Euro EUR 978 2
89 GABON CFA Franc BEAC XAF 950 0
90 GAMBIA (THE) Dalasi GMD 270 2
91 GEORGIA Lari GEL 981 2
92 GERMANY Euro EUR 978 2
93 GHANA Ghana Cedi GHS 936 2
94 GIBRALTAR Gibraltar Pound GIP 292 2
95 GREECE Euro EUR 978 2
96 GREENLAND Danish Krone DKK 208 2
97 GRENADA East Caribbean Dollar XCD 951 2
98 GUADELOUPE Euro EUR 978 2
99 GUAM US Dollar USD 840 2
100 GUATEMALA Quetzal GTQ 320 2
101 GUERNSEY Pound Sterling GBP 826 2
102 GUINEA Guinean Franc GNF 324 0
103 GUINEA-BISSAU CFA Franc BCEAO XOF 952 0
104 GUYANA Guyana Dollar GYD 328 2
105 HAITI Gourde HTG 332 2
106 HAITI US Dollar USD 840 2
107 HEARD ISLAND AND McDONALD ISLANDS Australian Dollar AUD 036 2
108 HOLY SEE (THE) Euro EUR 978 2
109 HONDURAS Lempira HNL 340 2
110 HONG KONG Hong Kong Dollar HKD 344 2
111 HUNGARY Forint HUF 348 2
112 ICELAND Iceland Krona ISK 352 0
113 INDIA Indian Rupee INR 356 2
114 INDONESIA Rupiah IDR 360 2
115 INTERNATIONAL MONETARY FUND (IMF) SDR (Special Drawing Right) XDR 960 -
116 IRAN (ISLAMIC REPUBLIC OF) Iranian Rial IRR 364 2
117 IRAQ Iraqi Dinar IQD 368 3
118 IRELAND Euro EUR 978 2
119 ISLE OF MAN Pound Sterling GBP 826 2
120 ISRAEL New Israeli Sheqel ILS 376 2
121 ITALY Euro EUR 978 2
122 JAMAICA Jamaican Dollar JMD 388 2
123 JAPAN Yen JPY 392 0
124 JERSEY Pound Sterling GBP 826 2
125 JORDAN Jordanian Dinar JOD 400 3
126 KAZAKHSTAN Tenge KZT 398 2
127 KENYA Kenyan Shilling KES 404 2
128 KIRIBATI Australian Dollar AUD 036 2
129 KOREA (THE DEMOCRATIC PEOPLE'S REPUBLIC OF) North Korean Won KPW 408 2
130 KOREA (THE REPUBLIC OF) Won KRW 410 0
131 KUWAIT Kuwaiti Dinar KWD 414 3
132 KYRGYZSTAN Som KGS 417 2
133 LAO PEOPLE'S DEMOCRATIC REPUBLIC (THE) Lao Kip LAK 418 2
134 LATVIA Euro EUR 978 2
135 LEBANON Lebanese Pound LBP 422 2
136 LESOTHO Loti LSL 426 2
137 LESOTHO Rand ZAR 710 2
138 LIBERIA Liberian Dollar LRD 430 2
139 LIBYA Libyan Dinar LYD 434 3
140 LIECHTENSTEIN Swiss Franc CHF 756 2
141 LITHUANIA Euro EUR 978 2
142 LUXEMBOURG Euro EUR 978 2
143 MACAO Pataca MOP 446 2
144 NORTH MACEDONIA Denar MKD 807 2
145 MADAGASCAR Malagasy Ariary MGA 969 2
146 MALAWI Malawi Kwacha MWK 454 2
147 MALAYSIA Malaysian Ringgit MYR 458 2
148 MALDIVES Rufiyaa MVR 462 2
149 MALI CFA Franc BCEAO XOF 952 0
150 MALTA Euro EUR 978 2
151 MARSHALL ISLANDS (THE) US Dollar USD 840 2
152 MARTINIQUE Euro EUR 978 2
153 MAURITANIA Ouguiya MRU 929 2
154 MAURITIUS Mauritius Rupee MUR 480 2
155 MAYOTTE Euro EUR 978 2
156 MEMBER COUNTRIES OF THE AFRICAN DEVELOPMENT BANK GROUP ADB Unit of Account XUA 965 -
157 MEXICO Mexican Peso MXN 484 2
158 MEXICO Mexican Unidad de Inversion (UDI) MXV 979 2
159 MICRONESIA (FEDERATED STATES OF) US Dollar USD 840 2
160 MOLDOVA (THE REPUBLIC OF) Moldovan Leu MDL 498 2
161 MONACO Euro EUR 978 2
162 MONGOLIA Tugrik MNT 496 2
163 MONTENEGRO Euro EUR 978 2
164 MONTSERRAT East Caribbean Dollar XCD 951 2
165 MOROCCO Moroccan Dirham MAD 504 2
166 MOZAMBIQUE Mozambique Metical MZN 943 2
167 MYANMAR Kyat MMK 104 2
168 NAMIBIA Namibia Dollar NAD 516 2
169 NAMIBIA Rand ZAR 710 2
170 NAURU Australian Dollar AUD 036 2
171 NEPAL Nepalese Rupee NPR 524 2
172 NETHERLANDS (THE) Euro EUR 978 2
173 NEW CALEDONIA CFP Franc XPF 953 0
174 NEW ZEALAND New Zealand Dollar NZD 554 2
175 NICARAGUA Cordoba Oro NIO 558 2
176 NIGER (THE) CFA Franc BCEAO XOF 952 0
177 NIGERIA Naira NGN 566 2
178 NIUE New Zealand Dollar NZD 554 2
179 NORFOLK ISLAND Australian Dollar AUD 036 2
180 NORTHERN MARIANA ISLANDS (THE) US Dollar USD 840 2
181 NORWAY Norwegian Krone NOK 578 2
182 OMAN Rial Omani OMR 512 3
183 PAKISTAN Pakistan Rupee PKR 586 2
184 PALAU US Dollar USD 840 2
185 PALESTINE, STATE OF No universal currency
186 PANAMA Balboa PAB 590 2
187 PANAMA US Dollar USD 840 2
188 PAPUA NEW GUINEA Kina PGK 598 2
189 PARAGUAY Guarani PYG 600 0
190 PERU Sol PEN 604 2
191 PHILIPPINES (THE) Philippine Peso PHP 608 2
192 PITCAIRN New Zealand Dollar NZD 554 2
193 POLAND Zloty PLN 985 2
194 PORTUGAL Euro EUR 978 2
195 PUERTO RICO US Dollar USD 840 2
196 QATAR Qatari Rial QAR 634 2
197 RÉUNION Euro EUR 978 2
198 ROMANIA Romanian Leu RON 946 2
199 RUSSIAN FEDERATION (THE) Russian Ruble RUB 643 2
200 RWANDA Rwanda Franc RWF 646 0
201 SAINT BARTHÉLEMY Euro EUR 978 2
202 SAINT HELENA, ASCENSION AND TRISTAN DA CUNHA Saint Helena Pound SHP 654 2
203 SAINT KITTS AND NEVIS East Caribbean Dollar XCD 951 2
204 SAINT LUCIA East Caribbean Dollar XCD 951 2
205 SAINT MARTIN (FRENCH PART) Euro EUR 978 2
206 SAINT PIERRE AND MIQUELON Euro EUR 978 2
207 SAINT VINCENT AND THE GRENADINES East Caribbean Dollar XCD 951 2
208 SAMOA Tala WST 882 2
209 SAN MARINO Euro EUR 978 2
210 SAO TOME AND PRINCIPE Dobra STN 930 2
211 SAUDI ARABIA Saudi Riyal SAR 682 2
212 SENEGAL CFA Franc BCEAO XOF 952 0
213 SERBIA Serbian Dinar RSD 941 2
214 SEYCHELLES Seychelles Rupee SCR 690 2
215 SIERRA LEONE Leone SLL 694 2
216 SINGAPORE Singapore Dollar SGD 702 2
217 SINT MAARTEN (DUTCH PART) Netherlands Antillean Guilder ANG 532 2
218 SISTEMA UNITARIO DE COMPENSACION REGIONAL DE PAGOS "SUCRE" Sucre XSU 994 -
219 SLOVAKIA Euro EUR 978 2
220 SLOVENIA Euro EUR 978 2
221 SOLOMON ISLANDS Solomon Islands Dollar SBD 090 2
222 SOMALIA Somali Shilling SOS 706 2
223 SOUTH AFRICA Rand ZAR 710 2
224 SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS No universal currency
225 SOUTH SUDAN South Sudanese Pound SSP 728 2
226 SPAIN Euro EUR 978 2
227 SRI LANKA Sri Lanka Rupee LKR 144 2
228 SUDAN (THE) Sudanese Pound SDG 938 2
229 SURINAME Surinam Dollar SRD 968 2
230 SVALBARD AND JAN MAYEN Norwegian Krone NOK 578 2
231 SWEDEN Swedish Krona SEK 752 2
232 SWITZERLAND Swiss Franc CHF 756 2
233 SWITZERLAND WIR Euro CHE 947 2
234 SWITZERLAND WIR Franc CHW 948 2
235 SYRIAN ARAB REPUBLIC Syrian Pound SYP 760 2
236 TAIWAN (PROVINCE OF CHINA) New Taiwan Dollar TWD 901 2
237 TAJIKISTAN Somoni TJS 972 2
238 TANZANIA, UNITED REPUBLIC OF Tanzanian Shilling TZS 834 2
239 THAILAND Baht THB 764 2
240 TIMOR-LESTE US Dollar USD 840 2
241 TOGO CFA Franc BCEAO XOF 952 0
242 TOKELAU New Zealand Dollar NZD 554 2
243 TONGA Pa'anga TOP 776 2
244 TRINIDAD AND TOBAGO Trinidad and Tobago Dollar TTD 780 2
245 TUNISIA Tunisian Dinar TND 788 3
246 TURKEY Turkish Lira TRY 949 2
247 TURKMENISTAN Turkmenistan New Manat TMT 934 2
248 TURKS AND CAICOS ISLANDS (THE) US Dollar USD 840 2
249 TUVALU Australian Dollar AUD 036 2
250 UGANDA Uganda Shilling UGX 800 0
251 UKRAINE Hryvnia UAH 980 2
252 UNITED ARAB EMIRATES (THE) UAE Dirham AED 784 2
253 UNITED KINGDOM OF GREAT BRITAIN AND NORTHERN IRELAND (THE) Pound Sterling GBP 826 2
254 UNITED STATES MINOR OUTLYING ISLANDS (THE) US Dollar USD 840 2
255 UNITED STATES OF AMERICA (THE) US Dollar USD 840 2
256 UNITED STATES OF AMERICA (THE) US Dollar (Next day) USN 997 2
257 URUGUAY Peso Uruguayo UYU 858 2
258 URUGUAY Uruguay Peso en Unidades Indexadas (UI) UYI 940 0
259 URUGUAY Unidad Previsional UYW 927 4
260 UZBEKISTAN Uzbekistan Sum UZS 860 2
261 VANUATU Vatu VUV 548 0
262 VENEZUELA (BOLIVARIAN REPUBLIC OF) Bolívar Soberano VES 928 2
263 VIET NAM Dong VND 704 0
264 VIRGIN ISLANDS (BRITISH) US Dollar USD 840 2
265 VIRGIN ISLANDS (U.S.) US Dollar USD 840 2
266 WALLIS AND FUTUNA CFP Franc XPF 953 0
267 WESTERN SAHARA Moroccan Dirham MAD 504 2
268 YEMEN Yemeni Rial YER 886 2
269 ZAMBIA Zambian Kwacha ZMW 967 2
270 ZIMBABWE Zimbabwe Dollar ZWL 932 2
271 ZZ01_Bond Markets Unit European_EURCO Bond Markets Unit European Composite Unit (EURCO) XBA 955 -
272 ZZ02_Bond Markets Unit European_EMU-6 Bond Markets Unit European Monetary Unit (E.M.U.-6) XBB 956 -
273 ZZ03_Bond Markets Unit European_EUA-9 Bond Markets Unit European Unit of Account 9 (E.U.A.-9) XBC 957 -
274 ZZ04_Bond Markets Unit European_EUA-17 Bond Markets Unit European Unit of Account 17 (E.U.A.-17) XBD 958 -
275 ZZ06_Testing_Code Codes specifically reserved for testing purposes XTS 963 -
276 ZZ07_No_Currency The codes assigned for transactions where no currency is involved XXX 999 -
277 ZZ08_Gold Gold XAU 959 -
278 ZZ09_Palladium Palladium XPD 964 -
279 ZZ10_Platinum Platinum XPT 962 -
280 ZZ11_Silver Silver XAG 961 -
281 AFGHANISTAN Afghani AFA 004 2003-01
282 ÅLAND ISLANDS Markka FIM 246 2002-03
283 ALBANIA Old Lek ALK 008 1989-12
284 ANDORRA Andorran Peseta ADP 020 2003-07
285 ANDORRA Spanish Peseta ESP 724 2002-03
286 ANDORRA French Franc FRF 250 2002-03
287 ANGOLA Kwanza AOK 024 1991-03
288 ANGOLA New Kwanza AON 024 2000-02
289 ANGOLA Kwanza Reajustado AOR 982 2000-02
290 ARGENTINA Austral ARA 032 1992-01
291 ARGENTINA Peso Argentino ARP 032 1985-07
292 ARGENTINA Peso ARY 032 1989 to 1990
293 ARMENIA Russian Ruble RUR 810 1994-08
294 AUSTRIA Schilling ATS 040 2002-03
295 AZERBAIJAN Azerbaijan Manat AYM 945 2005-10
296 AZERBAIJAN Azerbaijanian Manat AZM 031 2005-12
297 AZERBAIJAN Russian Ruble RUR 810 1994-08
298 BELARUS Belarusian Ruble BYB 112 2001-01
299 BELARUS Belarusian Ruble BYR 974 2017-01
300 BELARUS Russian Ruble RUR 810 1994-06
301 BELGIUM Convertible Franc BEC 993 1990-03
302 BELGIUM Belgian Franc BEF 056 2002-03
303 BELGIUM Financial Franc BEL 992 1990-03
304 BOLIVIA Peso boliviano BOP 068 1987-02
305 BOSNIA AND HERZEGOVINA Dinar BAD 070 1998-07
306 BRAZIL Cruzeiro BRB 076 1986-03
307 BRAZIL Cruzado BRC 076 1989-02
308 BRAZIL Cruzeiro BRE 076 1993-03
309 BRAZIL New Cruzado BRN 076 1990-03
310 BRAZIL Cruzeiro Real BRR 987 1994-07
311 BULGARIA Lev A/52 BGJ 100 1989 to 1990
312 BULGARIA Lev A/62 BGK 100 1989 to 1990
313 BULGARIA Lev BGL 100 2003-11
314 BURMA Kyat BUK 104 1990-02
315 CROATIA Croatian Dinar HRD 191 1995-01
316 CROATIA Croatian Kuna HRK 191 2015-06
317 CYPRUS Cyprus Pound CYP 196 2008-01
318 CZECHOSLOVAKIA Krona A/53 CSJ 203 1989 to 1990
319 CZECHOSLOVAKIA Koruna CSK 200 1993-03
320 ECUADOR Sucre ECS 218 2000-09
321 ECUADOR Unidad de Valor Constante (UVC) ECV 983 2000-09
322 EQUATORIAL GUINEA Ekwele GQE 226 1986-06
323 ESTONIA Kroon EEK 233 2011-01
324 EUROPEAN MONETARY CO-OPERATION FUND (EMCF) European Currency Unit (E.C.U) XEU 954 1999-01
325 FINLAND Markka FIM 246 2002-03
326 FRANCE French Franc FRF 250 2002-03
327 FRENCH GUIANA French Franc FRF 250 2002-03
328 FRENCH SOUTHERN TERRITORIES French Franc FRF 250 2002-03
329 GEORGIA Georgian Coupon GEK 268 1995-10
330 GEORGIA Russian Ruble RUR 810 1994-04
331 GERMAN DEMOCRATIC REPUBLIC Mark der DDR DDM 278 1990-07 to 1990-09
332 GERMANY Deutsche Mark DEM 276 2002-03
333 GHANA Cedi GHC 288 2008-01
334 GHANA Ghana Cedi GHP 939 2007-06
335 GREECE Drachma GRD 300 2002-03
336 GUADELOUPE French Franc FRF 250 2002-03
337 GUINEA Syli GNE 324 1989-12
338 GUINEA Syli GNS 324 1986-02
339 GUINEA-BISSAU Guinea Escudo GWE 624 1978 to 1981
340 GUINEA-BISSAU Guinea-Bissau Peso GWP 624 1997-05
341 HOLY SEE (VATICAN CITY STATE) Italian Lira ITL 380 2002-03
342 ICELAND Old Krona ISJ 352 1989 to 1990
343 IRELAND Irish Pound IEP 372 2002-03
344 ISRAEL Pound ILP 376 1978 to 1981
345 ISRAEL Old Shekel ILR 376 1989 to 1990
346 ITALY Italian Lira ITL 380 2002-03
347 KAZAKHSTAN Russian Ruble RUR 810 1994-05
348 KYRGYZSTAN Russian Ruble RUR 810 1993-01
349 LAO Pathet Lao Kip LAJ 418 1979-12
350 LATVIA Latvian Lats LVL 428 2014-01
351 LATVIA Latvian Ruble LVR 428 1994-12
352 LESOTHO Loti LSM 426 1985-05
353 LESOTHO Financial Rand ZAL 991 1995-03
354 LITHUANIA Lithuanian Litas LTL 440 2014-12
355 LITHUANIA Talonas LTT 440 1993-07
356 LUXEMBOURG Luxembourg Convertible Franc LUC 989 1990-03
357 LUXEMBOURG Luxembourg Franc LUF 442 2002-03
358 LUXEMBOURG Luxembourg Financial Franc LUL 988 1990-03
359 MADAGASCAR Malagasy Franc MGF 450 2004-12
360 MALAWI Kwacha MWK 454 2016-02
361 MALDIVES Maldive Rupee MVQ 462 1989-12
362 MALI Mali Franc MLF 466 1984-11
363 MALTA Maltese Lira MTL 470 2008-01
364 MALTA Maltese Pound MTP 470 1983-06
365 MARTINIQUE French Franc FRF 250 2002-03
366 MAURITANIA Ouguiya MRO 478 2017-12
367 MAYOTTE French Franc FRF 250 2002-03
368 MEXICO Mexican Peso MXP 484 1993-01
369 MOLDOVA, REPUBLIC OF Russian Ruble RUR 810 1993-12
370 MONACO French Franc FRF 250 2002-03
371 MOZAMBIQUE Mozambique Escudo MZE 508 1978 to 1981
372 MOZAMBIQUE Mozambique Metical MZM 508 2006-06
373 NETHERLANDS Netherlands Guilder NLG 528 2002-03
374 NETHERLANDS ANTILLES Netherlands Antillean Guilder ANG 532 2010-10
375 NICARAGUA Cordoba NIC 558 1990-10
376 PERU Sol PEH 604 1989 to 1990
377 PERU Inti PEI 604 1991-07
378 PERU Nuevo Sol PEN 604 2015-12
379 PERU Sol PES 604 1986-02
380 POLAND Zloty PLZ 616 1997-01
381 PORTUGAL Portuguese Escudo PTE 620 2002-03
382 RÉUNION French Franc FRF 250 2002-03
383 ROMANIA Leu A/52 ROK 642 1989 to 1990
384 ROMANIA Old Leu ROL 642 2005-06
385 ROMANIA New Romanian Leu RON 946 2015-06
386 RUSSIAN FEDERATION Russian Ruble RUR 810 2004-01
387 SAINT MARTIN French Franc FRF 250 1999-01
388 SAINT PIERRE AND MIQUELON French Franc FRF 250 2002-03
389 SAINT-BARTHÉLEMY French Franc FRF 250 1999-01
390 SAN MARINO Italian Lira ITL 380 2002-03
391 SAO TOME AND PRINCIPE Dobra STD 678 2017-12
392 SERBIA AND MONTENEGRO Serbian Dinar CSD 891 2006-10
393 SERBIA AND MONTENEGRO Euro EUR 978 2006-10
394 SLOVAKIA Slovak Koruna SKK 703 2009-01
395 SLOVENIA Tolar SIT 705 2007-01
396 SOUTH AFRICA Financial Rand ZAL 991 1995-03
397 SOUTH SUDAN Sudanese Pound SDG 938 2012-09
398 SOUTHERN RHODESIA Rhodesian Dollar RHD 716 1978 to 1981
399 SPAIN Spanish Peseta ESA 996 1978 to 1981
400 SPAIN "A" Account (convertible Peseta Account) ESB 995 1994-12
401 SPAIN Spanish Peseta ESP 724 2002-03
402 SUDAN Sudanese Dinar SDD 736 2007-07
403 SUDAN Sudanese Pound SDP 736 1998-06
404 SURINAME Surinam Guilder SRG 740 2003-12
405 SWAZILAND Lilangeni SZL 748 2018-08
406 SWITZERLAND WIR Franc (for electronic) CHC 948 2004-11
407 TAJIKISTAN Russian Ruble RUR 810 1995-05
408 TAJIKISTAN Tajik Ruble TJR 762 2001-04
409 TIMOR-LESTE Rupiah IDR 360 2002-07
410 TIMOR-LESTE Timor Escudo TPE 626 2002-11
411 TURKEY Old Turkish Lira TRL 792 2005-12
412 TURKEY New Turkish Lira TRY 949 2009-01
413 TURKMENISTAN Russian Ruble RUR 810 1993-10
414 TURKMENISTAN Turkmenistan Manat TMM 795 2009-01
415 UGANDA Uganda Shilling UGS 800 1987-05
416 UGANDA Old Shilling UGW 800 1989 to 1990
417 UKRAINE Karbovanet UAK 804 1996-09
418 UNION OF SOVIET SOCIALIST REPUBLICS Rouble SUR 810 1990-12
419 UNITED STATES US Dollar (Same day) USS 998 2014-03
420 URUGUAY Old Uruguay Peso UYN 858 1989-12
421 URUGUAY Uruguayan Peso UYP 858 1993-03
422 UZBEKISTAN Russian Ruble RUR 810 1994-07
423 VENEZUELA Bolivar VEB 862 2008-01
424 VENEZUELA Bolivar Fuerte VEF 937 2011-12
425 VENEZUELA (BOLIVARIAN REPUBLIC OF) Bolivar VEF 937 2016-02
426 VENEZUELA (BOLIVARIAN REPUBLIC OF) Bolívar VEF 937 2018-08
427 VIETNAM Old Dong VNC 704 1989-1990
428 YEMEN, DEMOCRATIC Yemeni Dinar YDD 720 1991-09
429 YUGOSLAVIA New Yugoslavian Dinar YUD 890 1990-01
430 YUGOSLAVIA New Dinar YUM 891 2003-07
431 YUGOSLAVIA Yugoslavian Dinar YUN 890 1995-11
432 ZAIRE New Zaire ZRN 180 1999-06
433 ZAIRE Zaire ZRZ 180 1994-02
434 ZAMBIA Zambian Kwacha ZMK 894 2012-12
435 ZIMBABWE Rhodesian Dollar ZWC 716 1989-12
436 ZIMBABWE Zimbabwe Dollar (old) ZWD 716 2006-08
437 ZIMBABWE Zimbabwe Dollar ZWD 716 2008-08
438 ZIMBABWE Zimbabwe Dollar (new) ZWN 942 2006-09
439 ZIMBABWE Zimbabwe Dollar ZWR 935 2009-06
440 ZZ01_Gold-Franc Gold-Franc XFO 2006-10
441 ZZ02_RINET Funds Code RINET Funds Code XRE 1999-11
442 ZZ05_UIC-Franc UIC-Franc XFU 2013-11

View File

@ -4,9 +4,20 @@
], ],
"copyright": "Copyright © 2024, Andrew Lalis", "copyright": "Copyright © 2024, Andrew Lalis",
"dependencies": { "dependencies": {
"handy-httpd": "~>8.4.0" "asdf": "~>0.7.17",
"botan": "~>1.13.6",
"d2sqlite3": "~>1.0.0",
"handy-httpd": "~>8.4.0",
"jwt": "~>0.4.0",
"slf4d": "~>3.0.1"
}, },
"description": "Backend API for Finnow.", "description": "Backend API for Finnow.",
"license": "proprietary", "license": "proprietary",
"name": "finnow-api" "name": "finnow-api",
"stringImportPaths": [
"."
],
"subConfigurations": {
"d2sqlite3": "all-included"
}
} }

View File

@ -1,9 +1,18 @@
{ {
"fileVersion": 1, "fileVersion": 1,
"versions": { "versions": {
"asdf": "0.7.17",
"botan": "1.13.6",
"botan-math": "1.0.4",
"d2sqlite3": "1.0.0",
"handy-httpd": "8.4.0", "handy-httpd": "8.4.0",
"httparsed": "1.2.1", "httparsed": "1.2.1",
"jwt": "0.4.0",
"memutils": "1.0.10",
"mir-algorithm": "3.22.1",
"mir-core": "1.7.1",
"path-matcher": "1.2.0", "path-matcher": "1.2.0",
"silly": "1.1.1",
"slf4d": "3.0.1", "slf4d": "3.0.1",
"streams": "3.5.0" "streams": "3.5.0"
} }

80
finnow-api/schema.sql Normal file
View File

@ -0,0 +1,80 @@
-- This schema is included at compile-time into data : SqliteDataSource.
-- Basic/Utility Entities
CREATE TABLE profile_property (
property TEXT PRIMARY KEY,
value TEXT DEFAULT NULL
);
CREATE TABLE attachment (
id INTEGER PRIMARY KEY,
uploaded_at TEXT NOT NULL,
filename TEXT NOT NULL,
content_type TEXT NOT NULL,
size INTEGER NOT NULL,
content BLOB NOT NULL
);
-- Account Entities
CREATE TABLE account (
id INTEGER PRIMARY KEY,
created_at TEXT NOT NULL,
archived BOOLEAN NOT NULL DEFAULT FALSE,
type TEXT NOT NULL,
number_suffix TEXT,
name TEXT NOT NULL,
currency TEXT NOT NULL,
description TEXT
);
CREATE TABLE account_credit_card_properties (
account_id INTEGER PRIMARY KEY,
credit_limit TEXT,
CONSTRAINT fk_account_credit_card_properties_account
FOREIGN KEY (account_id) REFERENCES account(id)
ON UPDATE CASCADE ON DELETE CASCADE
);
-- Transaction Entities
CREATE TABLE transaction_vendor (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
description TEXT
);
CREATE TABLE transaction_category (
id INTEGER PRIMARY KEY,
parent_id INTEGER,
name TEXT NOT NULL UNIQUE,
color TEXT NOT NULL DEFAULT 'FFFFFF',
CONSTRAINT fk_transaction_category_parent
FOREIGN KEY (parent_id) REFERENCES transaction_category(id)
ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE TABLE transaction_tag (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE
);
CREATE TABLE "transaction" (
id INTEGER PRIMARY KEY,
timestamp TEXT NOT NULL,
added_at TEXT NOT NULL,
amount TEXT NOT NULL,
currency TEXT NOT NULL,
description TEXT,
vendor_id INTEGER,
category_id INTEGER,
CONSTRAINT fk_transaction_vendor
FOREIGN KEY (vendor_id) REFERENCES transaction_vendor(id)
ON UPDATE CASCADE ON DELETE SET NULL,
CONSTRAINT fk_transaction_category
FOREIGN KEY (category_id) REFERENCES transaction_category(id)
ON UPDATE CASCADE ON DELETE SET NULL
);

View File

@ -1,17 +1,50 @@
import slf4d; import slf4d;
import handy_httpd; import handy_httpd;
import handy_httpd.handlers.path_handler; import handy_httpd.handlers.path_handler;
import handy_httpd.handlers.filtered_handler;
import model.base;
void main() { void main() {
ServerConfig cfg; ServerConfig cfg;
cfg.workerPoolSize = 5; cfg.workerPoolSize = 5;
cfg.port = 8080; cfg.port = 8080;
PathHandler pathHandler = new PathHandler(); HttpServer server = new HttpServer(buildHandlers(), cfg);
pathHandler.addMapping(Method.GET, "/status", (ref ctx) {
ctx.response.writeBodyString("online");
});
HttpServer server = new HttpServer(pathHandler, cfg);
server.start(); server.start();
} }
PathHandler buildHandlers() {
import profile;
const API_PATH = "/api";
PathHandler pathHandler = new PathHandler();
// Generic, public endpoints:
pathHandler.addMapping(Method.GET, API_PATH ~ "/status", (ref ctx) {
ctx.response.writeBodyString("online");
});
pathHandler.addMapping(Method.OPTIONS, API_PATH ~ "/**", (ref ctx) {});
// Auth Entrypoints:
import auth.api;
import auth.service;
pathHandler.addMapping(Method.POST, API_PATH ~ "/login", &postLogin);
pathHandler.addMapping(Method.POST, API_PATH ~ "/register", &postRegister);
// Authenticated endpoints:
PathHandler a = new PathHandler();
a.addMapping(Method.GET, API_PATH ~ "/me", &getMyUser);
a.addMapping(Method.GET, API_PATH ~ "/profiles", &handleGetProfiles);
a.addMapping(Method.POST, API_PATH ~ "/profiles", &handleCreateNewProfile);
a.addMapping(Method.DELETE, API_PATH ~ "/profiles/:name", &handleDeleteProfile);
a.addMapping(Method.GET, API_PATH ~ "/profiles/:profile/properties", &handleGetProperties);
a.addMapping(Method.GET, API_PATH ~ "/profiles/:profile/accounts", (ref ctx) {
ctx.response.writeBodyString("your accounts!");
});
HttpRequestFilter tokenAuthenticationFilter = new TokenAuthenticationFilter(SECRET);
pathHandler.addMapping(API_PATH ~ "/**", new FilteredRequestHandler(
a,
[tokenAuthenticationFilter]
));
return pathHandler;
}

View File

@ -0,0 +1,76 @@
/// API endpoints for authentication-related functions, like registration and login.
module auth.api;
import handy_httpd;
import handy_httpd.components.optional;
import slf4d;
import auth.model;
import auth.dao;
import auth.dto;
import auth.service;
void postLogin(ref HttpRequestContext ctx) {
LoginCredentials loginCredentials;
try {
loginCredentials = LoginCredentials.parse(ctx.request.readBodyAsJson());
} catch (Exception e) {
ctx.response.status = HttpStatus.BAD_REQUEST;
}
if (!validateUsername(loginCredentials.username)) {
ctx.response.status = HttpStatus.UNAUTHORIZED;
return;
}
UserRepository userRepo = new FileSystemUserRepository();
Optional!User optionalUser = userRepo.findByUsername(loginCredentials.username);
if (optionalUser.isNull) {
ctx.response.status = HttpStatus.UNAUTHORIZED;
return;
}
import botan.passhash.bcrypt : checkBcrypt;
if (!checkBcrypt(loginCredentials.password, optionalUser.value.passwordHash)) {
ctx.response.status = HttpStatus.UNAUTHORIZED;
return;
}
string token = generateAccessToken(optionalUser.value);
ctx.response.status = HttpStatus.OK;
ctx.response.writeBodyString(TokenResponse(token).toJson(), "application/json");
}
void postRegister(ref HttpRequestContext ctx) {
RegistrationData registrationData;
try {
registrationData = RegistrationData.parse(ctx.request.readBodyAsJson());
} catch (Exception e) {
ctx.response.status = HttpStatus.BAD_REQUEST;
return;
}
if (!validateUsername(registrationData.username)) {
ctx.response.status = HttpStatus.BAD_REQUEST;
ctx.response.writeBodyString("Invalid username.");
return;
}
if (!validatePassword(registrationData.password)) {
ctx.response.status = HttpStatus.BAD_REQUEST;
ctx.response.writeBodyString("Invalid password.");
return;
}
UserRepository userRepo = new FileSystemUserRepository();
if (!userRepo.findByUsername(registrationData.username).isNull) {
ctx.response.status = HttpStatus.BAD_REQUEST;
ctx.response.writeBodyString("Username is taken.");
return;
}
import botan.passhash.bcrypt : generateBcrypt;
import botan.rng.auto_rng;
RandomNumberGenerator rng = new AutoSeededRNG();
string passwordHash = generateBcrypt(registrationData.password, rng, 12);
userRepo.createUser(registrationData.username, passwordHash);
infoF!"Created user: %s"(registrationData.username);
}
void getMyUser(ref HttpRequestContext ctx) {
AuthContext auth = getAuthContext(ctx);
ctx.response.writeBodyString(auth.user.username);
}

View File

@ -0,0 +1,66 @@
module auth.dao;
import handy_httpd.components.optional;
import auth.model;
interface UserRepository {
Optional!User findByUsername(string username);
User createUser(string username, string passwordHash);
void deleteByUsername(string username);
}
/**
* User implementation that stores each user's data in a separate directory.
*/
class FileSystemUserRepository : UserRepository {
import std.path;
import std.file;
import std.json;
private string usersDir;
this(string usersDir = "users") {
this.usersDir = usersDir;
}
Optional!User findByUsername(string username) {
if (
!validateUsername(username) ||
!exists(getUserDir(username)) ||
!exists(getUserDataFile(username))
) {
return Optional!User.empty;
}
JSONValue userObj = parseJSON(readText(getUserDataFile(username)));
return Optional!User.of(User(
username,
userObj.object["passwordHash"].str
));
}
User createUser(string username, string passwordHash) {
if (!validateUsername(username)) throw new Exception("Invalid username");
if (exists(getUserDir(username))) throw new Exception("User already exists.");
JSONValue userObj = JSONValue.emptyObject;
userObj.object["passwordHash"] = JSONValue(passwordHash);
string jsonStr = userObj.toPrettyString();
if (!exists(this.usersDir)) mkdir(this.usersDir);
mkdir(getUserDir(username));
std.file.write(getUserDataFile(username), cast(ubyte[]) jsonStr);
return User(username, passwordHash);
}
void deleteByUsername(string username) {
if (validateUsername(username) && exists(getUserDir(username))) {
rmdirRecurse(getUserDir(username));
}
}
private string getUserDir(string username) {
return buildPath(this.usersDir, username);
}
private string getUserDataFile(string username) {
return buildPath(this.usersDir, username, "user-data.json");
}
}

View File

@ -0,0 +1,46 @@
/// Defines data-transfer objects for the API's authentication mechanisms.
module auth.dto;
import handy_httpd;
import std.json;
struct LoginCredentials {
string username;
string password;
static LoginCredentials parse(JSONValue obj) {
if (
obj.type != JSONType.OBJECT ||
"username" !in obj.object ||
"password" !in obj.object ||
obj.object["username"].type != JSONType.STRING ||
obj.object["password"].type != JSONType.STRING
) {
throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Malformed login credentials.");
}
return LoginCredentials(
obj.object["username"].str,
obj.object["password"].str
);
}
}
struct TokenResponse {
string token;
string toJson() {
JSONValue obj = JSONValue.emptyObject;
obj.object["token"] = JSONValue(token);
return obj.toString();
}
}
struct RegistrationData {
string username;
string password;
static RegistrationData parse(JSONValue obj) {
LoginCredentials lc = LoginCredentials.parse(obj);
return RegistrationData(lc.username, lc.password);
}
}

View File

@ -0,0 +1,41 @@
/**
* Defines models for the Finnow API's authentication system.
*/
module auth.model;
/**
* The user is the basic authenticated entity representing someone who has one
* or more profiles.
*/
struct User {
const string username;
const string passwordHash;
}
/**
* Validates a username string.
* Params:
* username = The username to check.
* Returns: True if the username is valid.
*/
bool validateUsername(string username) {
import std.regex;
import std.uni : toLower;
if (username is null || username.length < 3) return false;
const string[] RESERVED_USERNAMES = ["user", "admin"];
static foreach (reservedUsername; RESERVED_USERNAMES) {
if (toLower(username) == reservedUsername) return false;
}
auto r = ctRegex!(`^[a-zA-Z]+[a-zA-Z0-9_]+$`);
return !matchFirst(username, r).empty;
}
/**
* Validates that a password is of sufficient complexity.
* Params:
* password = The password to check.
* Returns: True if the password is sufficiently complex.
*/
bool validatePassword(string password) {
return password !is null && password.length >= 8;
}

View File

@ -0,0 +1,108 @@
module auth.service;
import handy_httpd;
import handy_httpd.components.optional;
import slf4d;
import auth.model;
import auth.dao;
import handy_httpd.handlers.filtered_handler;
const SECRET = "temporary-insecure-secret"; // TODO: Load secret from application config!
/**
* Generates a new JWT access token for a user.
* Params:
* user = The user to generate the token for.
* Returns: The token.
*/
string generateAccessToken(User user) {
import jwt.jwt : Token;
import jwt.algorithms : JWTAlgorithm;
import std.datetime;
const TIMEOUT_MINUTES = 30;
Token token = new Token(JWTAlgorithm.HS512);
token.claims.aud("finnow-api");
token.claims.sub(user.username);
token.claims.exp(Clock.currTime().toUnixTime() + TIMEOUT_MINUTES * 60);
token.claims.iss("finnow-api");
return token.encode(SECRET);
}
/**
* A request filter that only permits authenticated requests to be processed.
*/
class TokenAuthenticationFilter : HttpRequestFilter {
private static const AUTH_METADATA_KEY = "AuthContext";
private immutable string secret;
this(string secret) {
this.secret = secret;
}
void apply(ref HttpRequestContext ctx, FilterChain fc) {
Optional!AuthContext optionalAuth = validateAuthContext(ctx);
if (!optionalAuth.isNull) {
ctx.metadata[AUTH_METADATA_KEY] = optionalAuth.value;
fc.doFilter(ctx); // Only continue the filter chain if a valid auth context was obtained.
}
}
}
/// Information about the current request's authentication status.
class AuthContext {
string token;
User user;
this(string token, User user) {
this.token = token;
this.user = user;
}
}
/**
* Helper method to get the authentication context from a request context
* that was previously passed through this filter.
* Params:
* ctx = The request context to get.
* Returns: The auth context that has been set.
*/
AuthContext getAuthContext(ref HttpRequestContext ctx) {
return cast(AuthContext) ctx.metadata[TokenAuthenticationFilter.AUTH_METADATA_KEY];
}
private Optional!AuthContext validateAuthContext(ref HttpRequestContext ctx) {
import jwt.jwt : verify, Token;
import jwt.algorithms : JWTAlgorithm;
import std.typecons;
const HEADER_NAME = "Authorization";
if (!ctx.request.headers.contains(HEADER_NAME)) {
return setUnauthorized(ctx, "Missing Authorization header.");
}
string authorizationHeader = ctx.request.headers.getFirst(HEADER_NAME).orElse("");
if (authorizationHeader.length < 7 || authorizationHeader[0..7] != "Bearer ") {
return setUnauthorized(ctx, "Invalid Authorization header format. Expected bearer token.");
}
string rawToken = authorizationHeader[7..$];
try {
Token token = verify(rawToken, SECRET, [JWTAlgorithm.HS512]);
string username = token.claims.sub;
UserRepository userRepo = new FileSystemUserRepository();
Optional!User optionalUser = userRepo.findByUsername(username);
if (optionalUser.isNull) {
return setUnauthorized(ctx, "User does not exist.");
}
return Optional!AuthContext.of(new AuthContext(rawToken, optionalUser.value));
} catch (Exception e) {
warn("Failed to verify user token.", e);
return setUnauthorized(ctx, "Invalid or malformed token.");
}
}
private Optional!AuthContext setUnauthorized(ref HttpRequestContext ctx, string msg) {
ctx.response.status = HttpStatus.UNAUTHORIZED;
ctx.response.writeBodyString(msg);
return Optional!AuthContext.empty;
}

View File

@ -0,0 +1,2 @@
module data.account;

View File

@ -0,0 +1,15 @@
module data.base;
import model;
import handy_httpd.components.optional;
interface DataSource {
PropertiesRepository getPropertiesRepository();
}
interface PropertiesRepository {
Optional!string findProperty(string propertyName);
void setProperty(string name, string value);
void deleteProperty(string name);
ProfileProperty[] findAll();
}

View File

@ -0,0 +1,6 @@
module data;
public import data.base;
// Utility imports for items commonly used alongside data.
public import handy_httpd.components.optional;

View File

@ -0,0 +1,82 @@
module data.sqlite;
import d2sqlite3;
import slf4d;
import data.base;
import model;
class SqliteDataSource : DataSource {
const SCHEMA = import("schema.sql");
private const string dbPath;
private Database db;
this(string path) {
this.dbPath = path;
import std.file : exists;
bool needsInit = !exists(path);
this.db = Database(path);
if (needsInit) {
infoF!"Initializing database: %s"(dbPath);
db.run(SCHEMA);
}
}
PropertiesRepository getPropertiesRepository() {
return new SqlitePropertiesRepository(db);
}
}
class SqliteRepository {
private Database db;
this(Database db) {
this.db = db;
}
}
class SqlitePropertiesRepository : SqliteRepository, PropertiesRepository {
this(Database db) {
super(db);
}
Optional!string findProperty(string propertyName) {
Statement stmt = this.db.prepare("SELECT value FROM profile_property WHERE property = ?");
stmt.bind(1, propertyName);
ResultRange result = stmt.execute();
if (result.empty) return Optional!string.empty;
return Optional!string.of(result.front.peek!string(0));
}
void setProperty(string name, string value) {
if (findProperty(name).isNull) {
Statement stmt = this.db.prepare("INSERT INTO profile_property (property, value) VALUES (?, ?)");
stmt.bind(1, name);
stmt.bind(2, value);
stmt.execute();
} else {
Statement stmt = this.db.prepare("UPDATE profile_property SET value = ? WHERE property = ?");
stmt.bind(1, value);
stmt.bind(2, name);
stmt.execute();
}
}
void deleteProperty(string name) {
Statement stmt = this.db.prepare("DELETE FROM profile_property WHERE property = ?");
stmt.bind(1, name);
stmt.execute();
}
ProfileProperty[] findAll() {
Statement stmt = this.db.prepare("SELECT * FROM profile_property ORDER BY property ASC");
ResultRange result = stmt.execute();
ProfileProperty[] props;
foreach (Row row; result) {
ProfileProperty prop;
prop.property = row.peek!string("property");
prop.value = row.peek!string("value");
props ~= prop;
}
return props;
}
}

View File

@ -0,0 +1,20 @@
module model.account;
import model.base;
import std.datetime;
struct Account {
ulong id;
SysTime createdAt;
bool archived;
string type;
string numberSuffix;
string name;
string currency;
string description;
}
struct AccountCreditCardProperties {
ulong account_id;
string creditLimit;
}

View File

@ -1,37 +1,17 @@
module model.base; module model.base;
/** import std.datetime;
* The base class for all persistent entities with a unique integer id.
* It offers some basic utilities like equality and comparison by default.
*/
abstract class IdEntity {
const ulong id;
this(ulong id) { struct ProfileProperty {
this.id = id; string property;
} string value;
}
override bool opEquals(Object other) const { struct Attachment {
if (IdEntity e = cast(IdEntity) other) { ulong id;
return e.id == id; SysTime uploadedAt;
} string filename;
return false; string contentType;
} ulong size;
ubyte[] content;
override size_t toHash() const { }
import std.conv : to;
return id.to!size_t;
}
override string toString() const {
import std.format : format;
return format!"IdEntity(id = %d)"(id);
}
override int opCmp(Object other) const {
if (IdEntity e = cast(IdEntity) other) {
return this.id < e.id;
}
return 1;
}
}

View File

@ -0,0 +1,33 @@
module model.currency;
struct Currency {
const char[3] code;
const ubyte fractionalDigits;
const ushort numericCode;
import std.traits : isSomeString, EnumMembers;
static Currency ofCode(S)(S code) if (isSomeString!S) {
if (code.length != 3) {
throw new Exception("Invalid currency code: " ~ code);
}
static foreach (c; EnumMembers!Currencies) {
if (c.code == code) return c;
}
throw new Exception("Unknown currency code: " ~ code);
}
}
enum Currencies : Currency {
USD = Currency("USD", 2, 840),
CAD = Currency("CAD", 2, 124),
GBP = Currency("GBP", 2, 826),
EUR = Currency("EUR", 2, 978),
CHF = Currency("CHF", 2, 756),
ZAR = Currency("ZAR", 2, 710),
JPY = Currency("JPY", 0, 392),
INR = Currency("INR", 2, 356)
}
unittest {
assert(Currency.ofCode("USD") == Currencies.USD);
}

View File

@ -0,0 +1,8 @@
module model;
public import model.base;
public import model.account;
public import model.transaction;
// Additional utility imports used often alongside models.
public import handy_httpd.components.optional;

View File

@ -0,0 +1,33 @@
module model.transaction;
import std.datetime;
import model.currency;
struct TransactionVendor {
ulong id;
string name;
string description;
}
struct TransactionCategory {
ulong id;
ulong parentId;
string name;
string color;
}
struct TransactionTag {
ulong id;
string name;
}
struct Transaction {
ulong id;
SysTime timestamp;
SysTime addedAt;
string amount;
Currency currency;
string description;
ulong vendorId;
ulong categoryId;
}

214
finnow-api/source/profile.d Normal file
View File

@ -0,0 +1,214 @@
/**
* This module contains everything related to a user's Profiles, including
* data repositories, API endpoints, models, and logic.
*/
module profile;
import handy_httpd;
import handy_httpd.components.optional;
import auth.model : User;
import auth.service : AuthContext, getAuthContext;
import data;
import model;
import std.json;
import asdf;
const DEFAULT_USERS_DIR = "users";
/**
* A profile is a complete set of Finnow financial data, all stored in one
* single database file. The profile's name is used to lookup the database
* partition for its data. A user may own multiple profiles.
*/
class Profile {
string name;
this(string name) {
this.name = name;
}
override int opCmp(Object other) const {
if (Profile p = cast(Profile) other) {
return this.name < p.name;
}
return 1;
}
}
/**
* Validates a profile name.
* Params:
* name = The name to check.
* Returns: True if the profile name is valid.
*/
bool validateProfileName(string name) {
import std.regex;
import std.uni : toLower;
if (name is null || name.length < 3) return false;
auto r = ctRegex!(`^[a-zA-Z]+[a-zA-Z0-9_]+$`);
return !matchFirst(name, r).empty;
}
interface ProfileRepository {
Optional!Profile findByName(string name);
Profile createProfile(string name);
Profile[] findAll();
void deleteByName(string name);
DataSource getDataSource(in Profile profile);
}
class FileSystemProfileRepository : ProfileRepository {
import std.path;
import std.file;
import data;
import data.sqlite;
private const string usersDir;
private const string username;
this(string usersDir, string username) {
this.usersDir = usersDir;
this.username = username;
}
this(string username) {
this(DEFAULT_USERS_DIR, username);
}
Optional!Profile findByName(string name) {
string path = getProfilePath(name);
if (!exists(path)) return Optional!Profile.empty;
return Optional!Profile.of(new Profile(name));
}
Profile createProfile(string name) {
string path = getProfilePath(name);
if (exists(path)) throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Profile already exists.");
if (!exists(getProfilesDir())) mkdir(getProfilesDir());
DataSource ds = new SqliteDataSource(path);
import std.datetime;
auto propsRepo = ds.getPropertiesRepository();
propsRepo.setProperty("name", name);
propsRepo.setProperty("createdAt", Clock.currTime(UTC()).toISOExtString());
propsRepo.setProperty("user", username);
return new Profile(name);
}
Profile[] findAll() {
string profilesDir = getProfilesDir();
if (!exists(profilesDir)) return [];
Profile[] profiles;
foreach (DirEntry entry; dirEntries(profilesDir, SpanMode.shallow, false)) {
import std.string : endsWith;
const suffix = ".sqlite";
if (endsWith(entry.name, suffix)) {
string profileName = baseName(entry.name, suffix);
profiles ~= new Profile(profileName);
}
}
import std.algorithm.sorting : sort;
sort(profiles);
return profiles;
}
void deleteByName(string name) {
string path = getProfilePath(name);
if (exists(path)) {
std.file.remove(path);
}
}
DataSource getDataSource(in Profile profile) {
return new SqliteDataSource(getProfilePath(profile.name));
}
private string getProfilesDir() {
return buildPath(this.usersDir, username, "profiles");
}
private string getProfilePath(string name) {
return buildPath(this.usersDir, username, "profiles", name ~ ".sqlite");
}
}
// API Endpoints Below Here!
void handleCreateNewProfile(ref HttpRequestContext ctx) {
JSONValue obj = ctx.request.readBodyAsJson();
string name = obj.object["name"].str;
if (!validateProfileName(name)) {
ctx.response.status = HttpStatus.BAD_REQUEST;
ctx.response.writeBodyString("Invalid profile name.");
return;
}
AuthContext auth = getAuthContext(ctx);
ProfileRepository profileRepo = new FileSystemProfileRepository(auth.user.username);
profileRepo.createProfile(name);
}
void handleGetProfiles(ref HttpRequestContext ctx) {
AuthContext auth = getAuthContext(ctx);
ProfileRepository profileRepo = new FileSystemProfileRepository(auth.user.username);
Profile[] profiles = profileRepo.findAll();
ctx.response.writeBodyString(serializeToJson(profiles), "application/json");
}
void handleDeleteProfile(ref HttpRequestContext ctx) {
string name = ctx.request.getPathParamAs!string("name");
if (!validateProfileName(name)) {
ctx.response.status = HttpStatus.BAD_REQUEST;
ctx.response.writeBodyString("Invalid profile name.");
return;
}
AuthContext auth = getAuthContext(ctx);
ProfileRepository profileRepo = new FileSystemProfileRepository(auth.user.username);
profileRepo.deleteByName(name);
}
void handleGetProperties(ref HttpRequestContext ctx) {
ProfileContext profileCtx = getProfileContextOrThrow(ctx);
ProfileRepository profileRepo = new FileSystemProfileRepository(profileCtx.user.username);
DataSource ds = profileRepo.getDataSource(profileCtx.profile);
auto propsRepo = ds.getPropertiesRepository();
ProfileProperty[] props = propsRepo.findAll();
ctx.response.writeBodyString(serializeToJson(props), "application/json");
}
/// Contextual information that's available when handling requests under a profile.
struct ProfileContext {
const Profile profile;
const User user;
}
/**
* Tries to get a profile context from a request context. This will attempt to
* extract a "profile" path parameter and the authenticated user, and combine
* them into the ProfileContext.
* Params:
* ctx = The request context to read.
* Returns: An optional profile context.
*/
Optional!ProfileContext getProfileContext(ref HttpRequestContext ctx) {
import auth.service : AuthContext, getAuthContext;
if ("profile" !in ctx.request.pathParams) return Optional!ProfileContext.empty;
string profileName = ctx.request.pathParams["profile"];
if (!validateProfileName(profileName)) return Optional!ProfileContext.empty;
AuthContext authCtx = getAuthContext(ctx);
if (authCtx is null) return Optional!ProfileContext.empty;
User user = authCtx.user;
ProfileRepository repo = new FileSystemProfileRepository("users", user.username);
return repo.findByName(profileName)
.mapIfPresent!(p => ProfileContext(p, user));
}
/**
* Similar to `getProfileContext`, but throws an HttpStatusException with a
* 404 NOT FOUND status if no profile context could be obtained.
* Params:
* ctx = The request context to read.
* Returns: The profile context that was obtained.
*/
ProfileContext getProfileContextOrThrow(ref HttpRequestContext ctx) {
return getProfileContext(ctx).orElseThrow(() => new HttpStatusException(HttpStatus.NOT_FOUND));
}