1
1
import * as fs from 'fs' ;
2
2
import * as readline from 'readline' ;
3
- import md5 from 'apache- md5' ;
3
+ import * as md5 from 'md5' ;
4
4
import {
5
5
BaseAuthenticator ,
6
6
KoaContext ,
7
7
AuthStatus ,
8
8
AuthResult ,
9
9
} from '@vulcan-sql/serve/models' ;
10
10
import { VulcanExtensionId , VulcanInternalExtension } from '@vulcan-sql/core' ;
11
+ import { isEmpty } from 'lodash' ;
11
12
12
13
interface AuthUserOptions {
13
14
/* user name */
@@ -16,82 +17,89 @@ interface AuthUserOptions {
16
17
attr : { [ field : string ] : string | boolean | number } ;
17
18
}
18
19
19
- interface PasswordFileOptions {
20
+ interface HTPasswdFileOptions {
20
21
/** password file path */
21
22
[ 'path' ] : string ;
22
23
/** each user information */
23
24
[ 'users' ] : Array < AuthUserOptions > ;
24
25
}
25
26
26
- interface TokenListOptions {
27
+ export interface AuthUserListOptions {
27
28
/* user name */
28
29
name : string ;
29
- /* hashed password by apache md5 */
30
- hashPassword : string ;
30
+ /* hashed password by md5 */
31
+ md5Password : string ;
31
32
/* the user attribute which could used after auth successful */
32
33
attr : { [ field : string ] : string | boolean | number } ;
33
34
}
34
35
35
36
export interface BasicOptions {
36
- [ 'password -file' ] ?: PasswordFileOptions ;
37
- [ 'token- users' ] ?: Array < TokenListOptions > ;
37
+ [ 'htpasswd -file' ] ?: HTPasswdFileOptions ;
38
+ [ 'users-list ' ] ?: Array < AuthUserListOptions > ;
38
39
}
39
40
40
41
type UserCredentialsMap = {
41
42
[ name : string ] : {
42
- /* hashed password by apache md5 */
43
- hashPassword : string ;
43
+ /* hashed password by md5 */
44
+ md5Password : string ;
44
45
/* the user attribute which could used after auth successful */
45
46
attr : { [ field : string ] : string | boolean | number } ;
46
47
} ;
47
48
} ;
48
49
49
- /** The http basic authenticator */
50
- @VulcanInternalExtension ( )
50
+ /** The http basic authenticator.
51
+ *
52
+ * Able to set user credentials by file path through "htpasswd-file" or list directly in config by "users-list".
53
+ * The password must hash by md5 when setting into "htpasswd-file" or "users-list".
54
+ *
55
+ * It authenticate by passing encode base64 {username}:{password} to authorization
56
+ */
57
+ @VulcanInternalExtension ( 'auth' )
51
58
@VulcanExtensionId ( 'basic' )
52
59
export class BasicAuthenticator extends BaseAuthenticator < BasicOptions > {
53
60
private usersCredentials : UserCredentialsMap = { } ;
54
61
private options : BasicOptions = { } ;
55
62
/** read basic options to initialize and load user credentials */
56
63
public override async onActivate ( ) {
57
64
this . options = ( this . getOptions ( ) as BasicOptions ) || this . options ;
58
- // load "token-users" in options
59
- for ( const option of this . options [ 'token-users' ] || [ ] ) {
60
- const { name, hashPassword, attr } = option ;
61
- this . isMD5Hashed ( hashPassword ) ;
62
- this . usersCredentials [ name ] = { hashPassword, attr } ;
65
+ // load "users-list" in options
66
+ for ( const option of this . options [ 'users-list' ] || [ ] ) {
67
+ const { name, md5Password, attr } = option ;
68
+ this . usersCredentials [ name ] = { md5Password, attr } ;
63
69
}
64
- // load "password -file" in options
65
- if ( ! this . options [ 'password -file' ] ) return ;
66
- const { path, users } = this . options [ 'password -file' ] ;
70
+ // load "htpasswd -file" in options
71
+ if ( ! this . options [ 'htpasswd -file' ] ) return ;
72
+ const { path, users } = this . options [ 'htpasswd -file' ] ;
67
73
68
74
if ( ! fs . existsSync ( path ) || ! fs . statSync ( path ) . isFile ( ) ) return ;
69
75
const reader = readline . createInterface ( {
70
76
input : fs . createReadStream ( path ) ,
71
77
} ) ;
72
- // username:hashPassword
78
+ // username:md5Password
73
79
for await ( const line of reader ) {
74
80
const name = line . split ( ':' ) [ 0 ] || '' ;
75
- const hashPassword = line . split ( ':' ) [ 1 ] || '' ;
76
- this . isMD5Hashed ( hashPassword ) ;
81
+ const md5Password = line . split ( ':' ) [ 1 ] || '' ;
77
82
// if users exist the same name, add attr to here, or as empty
78
83
this . usersCredentials [ name ] = {
79
- hashPassword ,
80
- attr : users . find ( ( user ) => user . name === name ) ?. attr || { } ,
84
+ md5Password ,
85
+ attr : users ? .find ( ( user ) => user . name === name ) ?. attr || { } ,
81
86
} ;
82
87
}
83
88
}
84
89
85
90
public async authenticate ( context : KoaContext ) {
91
+ const incorrect = {
92
+ status : AuthStatus . INCORRECT ,
93
+ type : this . getExtensionId ( ) ! ,
94
+ } ;
95
+ if ( isEmpty ( this . options ) ) return incorrect ;
96
+
86
97
const authRequest = context . request . headers [ 'authorization' ] ;
87
98
if (
88
99
! authRequest ||
89
100
! authRequest . toLowerCase ( ) . startsWith ( this . getExtensionId ( ) ! )
90
101
)
91
- return {
92
- status : AuthStatus . INCORRECT ,
93
- type : this . getExtensionId ( ) ! ,
94
- } ;
102
+ return incorrect ;
95
103
96
104
// validate request auth token
97
105
const token = authRequest . trim ( ) . split ( ' ' ) [ 1 ] ;
@@ -117,10 +125,10 @@ export class BasicAuthenticator extends BaseAuthenticator<BasicOptions> {
117
125
// if authenticated, return user data
118
126
if (
119
127
! ( username in this . usersCredentials ) ||
120
- ! ( md5 ( password ) === this . usersCredentials [ username ] . hashPassword )
128
+ ! ( md5 ( password ) === this . usersCredentials [ username ] . md5Password )
121
129
)
122
130
throw new Error (
123
- `authenticate user by ${ this . getExtensionId ( ) } type failed.`
131
+ `authenticate user by " ${ this . getExtensionId ( ) } " type failed.`
124
132
) ;
125
133
126
134
return {
@@ -132,9 +140,4 @@ export class BasicAuthenticator extends BaseAuthenticator<BasicOptions> {
132
140
} ,
133
141
} as AuthResult ;
134
142
}
135
-
136
- private isMD5Hashed ( value : string ) {
137
- if ( ! value . startsWith ( '$apr1$' ) )
138
- throw new Error ( `"${ this . getExtensionId ( ) } " type must hash apache md5.` ) ;
139
- }
140
143
}
0 commit comments